gizmoControls.py (24345B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\studio\gizmoControls.py 2 import carbonui 3 import carbonui.text.styles 4 import eveicon 5 import geo2 6 import math 7 import uthread2 8 from carbonui import Align 9 from carbonui.button.menu import MenuButtonIcon 10 from carbonui.control.checkbox import Checkbox 11 from carbonui.control.contextMenu.menuData import MenuData 12 from carbonui.control.singlelineedits.singleLineEditFloat import SingleLineEditFloat 13 from carbonui.control.slider import Slider 14 from carbonui.primitives.container import Container 15 from carbonui.primitives.containerAutoSize import ContainerAutoSize 16 from carbonui.primitives.sprite import Sprite 17 from carbonui.uicore import uicore 18 from carbonui.util.various_unsorted import GetWindowAbove 19 from cosmetics.client.ships.qa.settings import scene_debug_mode_setting 20 from cosmetics.client.ships.skins.live_data import current_skin_design, current_skin_design_signals 21 from cosmetics.common.ships.skins.static_data.pattern_attribute import PatternAttribute 22 from cosmetics.common.ships.skins.static_data.slot_name import PATTERN_SLOT_IDS, PATTERN_SLOT_ID_BY_PATTERN_MATERIAL_ID 23 from eve.client.script.ui import eveColor 24 from eve.client.script.ui.camera.skinrShipSceneContainerCamera import ROLL_SNAP_STEP 25 from eve.client.script.ui.control.toggleButtonGroup import ToggleButtonGroup 26 from eve.client.script.ui.control.toggleButtonGroupButton import ToggleButtonGroupButton 27 from eve.client.script.ui.cosmetics.ship.pages.studio import studioSettings 28 from eve.client.script.ui.cosmetics.ship.pages.studio.studioSettings import pattern_rotation_follow_camera_setting 29 from localization import GetByLabel 30 DEFAULT_TOP_DOWN_VIEW = (0.999, 0.999) 31 DEFAULT_FRONTAL_VIEW = (1.0, 0.0) 32 DEFAULT_SIDE_RIGHT_VIEW = (0.5, 0.0) 33 34 class GizmoControls(ContainerAutoSize): 35 default_width = 522 36 default_alignMode = Align.TOTOP 37 BUTTON_ID_OFFSET = 1 38 BUTTON_ID_ORBITAL = 2 39 BUTTON_ID_ROTATE_SCALE = 3 40 41 def __init__(self, *args, **kwargs): 42 super(GizmoControls, self).__init__(*args, **kwargs) 43 self._radius = 0.0 44 self.construct_layout() 45 self.connect_signals() 46 47 def Close(self): 48 try: 49 self.disconnect_signals() 50 finally: 51 super(GizmoControls, self).Close() 52 53 def connect_signals(self): 54 current_skin_design_signals.on_component_attribute_changed.connect(self.on_component_attribute_changed) 55 current_skin_design_signals.on_slot_fitting_changed.connect(self.on_slot_fitting_changed) 56 current_skin_design_signals.on_slot_selected.connect(self.on_slot_selected) 57 current_skin_design_signals.on_existing_design_loaded.connect(self.on_existing_design_loaded) 58 59 def disconnect_signals(self): 60 current_skin_design_signals.on_component_attribute_changed.disconnect(self.on_component_attribute_changed) 61 current_skin_design_signals.on_slot_fitting_changed.disconnect(self.on_slot_fitting_changed) 62 current_skin_design_signals.on_slot_selected.disconnect(self.on_slot_selected) 63 current_skin_design_signals.on_existing_design_loaded.disconnect(self.on_existing_design_loaded) 64 65 def on_existing_design_loaded(self, *args): 66 self.update_values() 67 68 def on_slot_selected(self, slot_id): 69 self.update_visibility() 70 71 def on_slot_fitting_changed(self, slot_id, component_instance): 72 self.update_visibility() 73 74 def update_visibility(self): 75 if current_skin_design.get_selected_pattern_component_instance(): 76 self.display = True 77 self.update_values() 78 else: 79 self.display = False 80 81 def update_values(self): 82 self.offset_u_slider_cont.update_value() 83 self.offset_v_slider_cont.update_value() 84 self.yaw_slider_cont.update_value() 85 self.pitch_slider_cont.update_value() 86 self.scale_slider_cont.update_value() 87 self.roll_slider_cont.update_value() 88 89 def construct_layout(self): 90 self.construct_top_container() 91 self.construct_planar_controls() 92 self.construct_camera() 93 self.construct_buttons_and_panels() 94 self._debug_label = carbonui.text.styles.TextHeadline(parent=self, align=Align.TOTOP, display=scene_debug_mode_setting.is_enabled()) 95 self._debug_label_thread = None 96 self._update_scene_debug_thread() 97 scene_debug_mode_setting.on_change.connect(self.on_scene_debug_mode_setting) 98 99 def _update_scene_debug_thread(self): 100 if scene_debug_mode_setting.is_enabled(): 101 self._debug_label.display = True 102 self._debug_label_thread = uthread2.start_tasklet(self._update_debug_label_thread) 103 else: 104 self._debug_label.display = False 105 if self._debug_label_thread: 106 self._debug_label_thread.kill() 107 self._debug_label_thread = None 108 109 def on_scene_debug_mode_setting(self, value): 110 self._update_scene_debug_thread() 111 112 def _update_debug_label_thread(self): 113 while not self.destroyed: 114 uthread2.Sleep(0.1) 115 slot_id = current_skin_design.get_selected_slot_id() 116 if slot_id not in PATTERN_SLOT_IDS: 117 continue 118 component_instance = current_skin_design.get().get_fitted_component_instance(slot_id) 119 if component_instance: 120 yaw, pitch, roll = geo2.QuaternionRotationGetYawPitchRoll(component_instance.rotation) 121 text = 'Yaw={:.2f}, Pitch={:.2f} Roll={:.2f}\nPosition={:.2f}, {:.2f}, {:.2f}'.format(math.degrees(yaw), math.degrees(pitch), math.degrees(roll), *component_instance.position) 122 else: 123 text = '' 124 self._debug_label.text = text 125 126 def construct_top_container(self): 127 self.top_container = Container(name='top_container', parent=self, align=Align.TOTOP, height=32) 128 129 def construct_planar_controls(self): 130 planar_container = Container(name='planar_container', parent=self.top_container, align=Align.TORIGHT, width=24, padLeft=8) 131 MenuButtonIcon(name='planar_menu', parent=planar_container, align=Align.CENTER, texturePath='res:/UI/Texture/classes/Cosmetics/Ship/Pages/Studio/gizmo/pattern_projection.png', iconSize=24, hint=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/ProjectionPresets'), get_menu_func=self.get_planar_menu, pos=(0, 0, 24, 24)) 132 133 def get_planar_menu(self): 134 menu = MenuData() 135 menu.AddEntry(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/PatternProjectionTop'), texturePath='res:/UI/Texture/classes/Cosmetics/Ship/Pages/Studio/gizmo/pattern_projection_top_down.png', func=self.on_planar_mode_top_button) 136 menu.AddEntry(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/PatternProjectionFront'), texturePath='res:/UI/Texture/classes/Cosmetics/Ship/Pages/Studio/gizmo/pattern_projection_front_side.png', func=self.on_planar_mode_front_button) 137 menu.AddEntry(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/PatternProjectionRight'), texturePath='res:/UI/Texture/classes/Cosmetics/Ship/Pages/Studio/gizmo/pattern_projection_right_side.png', func=self.on_planar_mode_right_button) 138 return menu 139 140 def on_planar_mode_top_button(self, *args): 141 self._reset_pattern_yaw_pitch(*DEFAULT_TOP_DOWN_VIEW) 142 143 def on_planar_mode_front_button(self, *args): 144 self._reset_pattern_yaw_pitch(*DEFAULT_FRONTAL_VIEW) 145 146 def on_planar_mode_right_button(self, *args): 147 self._reset_pattern_yaw_pitch(*DEFAULT_SIDE_RIGHT_VIEW) 148 149 def _reset_pattern_yaw_pitch(self, yaw, pitch): 150 selected_slot_id = current_skin_design.get_selected_slot_id() 151 pattern_slot_id = PATTERN_SLOT_ID_BY_PATTERN_MATERIAL_ID.get(selected_slot_id, selected_slot_id) 152 component_instance = current_skin_design.get().get_fitted_component_instance(pattern_slot_id) 153 if component_instance: 154 component_instance.yaw = yaw 155 component_instance.pitch = pitch 156 if studioSettings.pattern_rotation_follow_camera_setting.is_enabled(): 157 studioSettings.pattern_rotation_follow_camera_setting.disable() 158 else: 159 current_skin_design.add_to_undo_history() 160 161 def construct_camera(self): 162 self.camera_container = ContainerAutoSize(name='camera_container', parent=self.top_container, align=Align.TORIGHT, height=32, padLeft=8) 163 self.camera_checkbox = Checkbox(name='camera_checkbox', parent=self.camera_container, align=Align.TORIGHT, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/CameraToggle'), hint=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/CameraToggleHint'), setting=pattern_rotation_follow_camera_setting, padTop=4) 164 165 def construct_buttons_and_panels(self): 166 self.button_container = Container(name='button_container', parent=self.top_container, align=Align.TOTOP, height=32) 167 self.button_group = ToggleButtonGroup(name='button_group', parent=self.button_container, align=Align.TOTOP) 168 self.panel_container = ContainerAutoSize(name='panel_container', parent=self, padTop=4, align=Align.TOTOP) 169 self.construct_orbit_panel() 170 self.construct_offset_panel() 171 self.construct_rotate_scale_panel() 172 self.button_group.AddButton(btnID=self.BUTTON_ID_ORBITAL, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/OrbitalPanelButton'), panel=self.orbital_panel, btnClass=OrbitButton) 173 self.button_group.AddButton(btnID=self.BUTTON_ID_OFFSET, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/OffsetPanelButton'), panel=self.offset_panel, btnClass=OffsetButton) 174 self.button_group.AddButton(btnID=self.BUTTON_ID_ROTATE_SCALE, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/RotateScalePanelButton'), panel=self.rotate_scale_panel, btnClass=RotateAndScaleButton) 175 self.button_group.SetSelected(self.BUTTON_ID_ORBITAL) 176 177 def construct_offset_panel(self): 178 self.offset_panel = ContainerAutoSize(name='offset_panel', parent=self.panel_container, display=False, align=Align.TOTOP) 179 self.offset_u_slider_cont = PatternAttributeSliderContainerOffsetU(parent=self.offset_panel, pattern_attribute_id=PatternAttribute.OFFSET_U_RATIO, icon_texture=eveicon.caret_left_right) 180 self.offset_v_slider_cont = PatternAttributeSliderContainerOffsetV(parent=self.offset_panel, pattern_attribute_id=PatternAttribute.OFFSET_V_RATIO, icon_texture=eveicon.caret_up_down) 181 182 def construct_orbit_panel(self): 183 self.orbital_panel = ContainerAutoSize(name='orbital_panel', parent=self.panel_container, display=False, align=Align.TOTOP) 184 self.yaw_slider_cont = PatternAttributeSliderContainerYaw(parent=self.orbital_panel, pattern_attribute_id=PatternAttribute.YAW_RATIO, icon_texture=eveicon.arrow_rotate_left) 185 self.pitch_slider_cont = PatternAttributeSliderContainerPitch(parent=self.orbital_panel, pattern_attribute_id=PatternAttribute.PITCH_RATIO, icon_texture=eveicon.arrow_rotate_right) 186 187 def construct_rotate_scale_panel(self): 188 self.rotate_scale_panel = ContainerAutoSize(name='rotate_scale_panel', parent=self.panel_container, display=False, align=Align.TOTOP) 189 self.roll_slider_cont = PatternAttributeSliderContainerRoll(name='roll_slider', parent=self.rotate_scale_panel, pattern_attribute_id=PatternAttribute.ROTATION, icon_texture=eveicon.orbit) 190 self.scale_slider_cont = PatternAttributeSliderContainerScaling(name='scale_slider', parent=self.rotate_scale_panel, pattern_attribute_id=PatternAttribute.SCALE, icon_texture=eveicon.expand) 191 192 def on_component_attribute_changed(self, slot_id, attribute_id, value): 193 current_pattern_slot = PATTERN_SLOT_ID_BY_PATTERN_MATERIAL_ID.get(current_skin_design.get_selected_slot_id(), slot_id) 194 if slot_id not in PATTERN_SLOT_IDS or slot_id != current_pattern_slot: 195 return 196 if attribute_id in (PatternAttribute.YAW_RATIO, PatternAttribute.PITCH_RATIO): 197 if self.button_group.GetValue() is not self.BUTTON_ID_ORBITAL: 198 self.button_group.SetSelected(self.BUTTON_ID_ORBITAL, animate=False) 199 elif attribute_id in (PatternAttribute.OFFSET_U_RATIO, PatternAttribute.OFFSET_V_RATIO): 200 if self.button_group.GetValue() is not self.BUTTON_ID_OFFSET: 201 self.button_group.SetSelected(self.BUTTON_ID_OFFSET, animate=False) 202 elif attribute_id in (PatternAttribute.ROLL_RATIO, PatternAttribute.SCALE): 203 if self.button_group.GetValue() is not self.BUTTON_ID_ROTATE_SCALE: 204 self.button_group.SetSelected(self.BUTTON_ID_ROTATE_SCALE, animate=False) 205 206 def update_radial_position(self): 207 wnd = GetWindowAbove(self) 208 if not wnd: 209 return 210 width, _ = wnd.GetAbsoluteSize() 211 offset = 48 if width < 1400 else 0 212 self.top = 64 + offset 213 214 @property 215 def radius(self): 216 return self._radius 217 218 @radius.setter 219 def radius(self, value): 220 self._radius = value 221 self.update_radial_position() 222 223 224 class BasePatternAttributeSliderContainer(Container): 225 default_height = 32 226 default_align = Align.TOTOP 227 min_value = -1.0 228 max_value = 1.0 229 decimal_places = 2 230 231 def __init__(self, pattern_attribute_id, icon_texture, *args, **kwargs): 232 super(BasePatternAttributeSliderContainer, self).__init__(*args, **kwargs) 233 self.pattern_attribute_id = pattern_attribute_id 234 self.icon_texture = icon_texture 235 self.construct_layout() 236 self.connect_signals() 237 238 def Close(self): 239 try: 240 self.disconnect_signals() 241 finally: 242 super(BasePatternAttributeSliderContainer, self).Close() 243 244 def construct_layout(self): 245 self.icon_container = Container(name='icon_container', parent=self, align=Align.TOLEFT, width=16, padRight=8) 246 self.icon = Sprite(name='icon', parent=self.icon_container, align=Align.CENTER, texturePath=self.icon_texture, width=16, height=16, color=eveColor.SILVER_GREY) 247 self.value_edit = SingleLineEditFloat(name='value_edit', parent=self, align=Align.TORIGHT, width=80, padding=(4, 2, 0, 2), minValue=self.value_to_readable_value(self.min_value), maxValue=self.value_to_readable_value(self.max_value), OnChange=self.on_value_edit_changed, OnFocusLost=self.on_value_edit_focus_lost, OnReturn=self.on_value_edit_return, decimalPlaces=self.decimal_places) 248 self.slider = Slider(name='slider', parent=self, getHintFunc=self.get_slider_hint, on_dragging=self.on_slider_value_changed, callback=self.on_slider_callback, barHeight=6, handleSize=28, align=Align.VERTICALLY_CENTERED, minValue=self.min_value, maxValue=self.max_value) 249 250 def on_slider_callback(self, *args): 251 self._apply_value_to_component_instance(self.slider.value) 252 current_skin_design.add_to_undo_history() 253 254 def on_value_edit_focus_lost(self, *args): 255 self._update_slider_and_edit_value() 256 current_skin_design.add_to_undo_history() 257 258 def on_value_edit_return(self, *args): 259 component_instance = current_skin_design.get_selected_pattern_component_instance() 260 if component_instance: 261 value = self.get_component_instance_value(component_instance) 262 readable_value = self.value_to_readable_value(value) 263 self.value_edit.SetValue(readable_value, docallback=False) 264 current_skin_design.add_to_undo_history() 265 266 def connect_signals(self): 267 current_skin_design_signals.on_component_attribute_changed.connect(self.on_component_attribute_changed) 268 269 def disconnect_signals(self): 270 current_skin_design_signals.on_component_attribute_changed.disconnect(self.on_component_attribute_changed) 271 272 def on_component_attribute_changed(self, slot_id, attribute_id, value): 273 if self.pattern_attribute_id == attribute_id: 274 self._update_slider_and_edit_value() 275 276 def update_value(self): 277 self._update_slider_and_edit_value() 278 279 def on_slider_value_changed(self, slider): 280 self._apply_value_to_component_instance(self.slider.value) 281 282 def on_value_edit_changed(self, *args): 283 self._apply_value_to_component_instance(self.readable_value_to_value(self.value_edit.GetValue())) 284 285 def _apply_value_to_component_instance(self, value): 286 component_instance = current_skin_design.get_selected_pattern_component_instance() 287 if component_instance: 288 self.set_component_instance_value(component_instance, value) 289 290 def _update_slider_and_edit_value(self): 291 component_instance = current_skin_design.get_selected_pattern_component_instance() 292 if component_instance: 293 value = self.get_component_instance_value(component_instance) 294 self.slider.SetValue(value) 295 readable_value = self.value_to_readable_value(value) 296 if uicore.registry.GetFocus() != self.value_edit: 297 self.value_edit.SetValue(readable_value, docallback=False) 298 299 def set_component_instance_value(self, component_instance, value): 300 raise NotImplementedError 301 302 def get_component_instance_value(self, component_instance): 303 raise NotImplementedError 304 305 def get_readable_value(self): 306 component_instance = self.get_component_instance() 307 if not component_instance: 308 return 0.0 309 return self.value_to_readable_value(self.get_component_instance_value(component_instance)) 310 311 def get_component_instance(self): 312 return current_skin_design.get_selected_pattern_component_instance() 313 314 def readable_value_to_value(self, readable_value): 315 return readable_value 316 317 def value_to_readable_value(self, value): 318 return value 319 320 def get_slider_hint(self): 321 return str(self.get_readable_value()) 322 323 324 class PatternAttributeSliderContainerScaling(BasePatternAttributeSliderContainer): 325 min_value = 0.0 326 327 def set_component_instance_value(self, component_instance, value): 328 component_instance.scaling_ratio = value 329 330 def get_component_instance_value(self, component_instance): 331 return component_instance.scaling_ratio 332 333 def get_slider_hint(self, *args): 334 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/ScaleAmountHint', amount=self.get_readable_value()) 335 336 def value_to_readable_value(self, value): 337 return 2.0 * value 338 339 def readable_value_to_value(self, readable_value): 340 return readable_value / 2.0 341 342 343 class PatternAttributeSliderContainerYaw(BasePatternAttributeSliderContainer): 344 min_value = -0.999 345 max_value = 0.999 346 decimal_places = 1 347 348 def set_component_instance_value(self, component_instance, value): 349 component_instance.yaw = value 350 351 def get_component_instance_value(self, component_instance): 352 return component_instance.yaw 353 354 def get_slider_hint(self, *args): 355 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/HorizontalOrbitHint', amount=self.get_readable_value()) 356 357 def value_to_readable_value(self, value): 358 value = value / (self.max_value - self.min_value) + 0.5 359 return value * 360.0 360 361 def readable_value_to_value(self, readable_value): 362 value = readable_value / 360.0 - 0.5 363 return value * (self.max_value - self.min_value) 364 365 366 class PatternAttributeSliderContainerPitch(BasePatternAttributeSliderContainer): 367 min_value = -0.999 368 max_value = 0.999 369 decimal_places = 1 370 371 def set_component_instance_value(self, component_instance, value): 372 component_instance.pitch = value 373 374 def get_component_instance_value(self, component_instance): 375 return component_instance.pitch 376 377 def value_to_readable_value(self, value): 378 value /= self.max_value - self.min_value 379 return value * 180.0 380 381 def readable_value_to_value(self, readable_value): 382 value = readable_value / 180.0 383 return value * (self.max_value - self.min_value) 384 385 def get_slider_hint(self, *args): 386 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/VerticalOrbitHint', amount=self.get_readable_value()) 387 388 389 class PatternAttributeSliderContainerRoll(BasePatternAttributeSliderContainer): 390 decimal_places = 1 391 392 def set_component_instance_value(self, component_instance, value): 393 component_instance.roll = value 394 395 def get_component_instance_value(self, component_instance): 396 return component_instance.roll 397 398 def get_slider_hint(self, *args): 399 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/RotateAmountHint', amount=self.get_readable_value()) 400 401 def value_to_readable_value(self, value): 402 return value * 180.0 403 404 def readable_value_to_value(self, readable_value): 405 return readable_value / 180.0 406 407 408 class PatternAttributeSliderContainerOffsetU(BasePatternAttributeSliderContainer): 409 decimal_places = 1 410 411 def set_component_instance_value(self, component_instance, value): 412 component_instance.offset_u_ratio = value 413 414 def get_component_instance_value(self, component_instance): 415 return component_instance._offset_u_ratio 416 417 def get_slider_hint(self, *args): 418 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/HorizontalOffsetHint', amount=self.get_readable_value()) 419 420 def value_to_readable_value(self, value): 421 return value * 100.0 422 423 def readable_value_to_value(self, readable_value): 424 return readable_value / 100.0 425 426 427 class PatternAttributeSliderContainerOffsetV(BasePatternAttributeSliderContainer): 428 decimal_places = 1 429 430 def set_component_instance_value(self, component_instance, value): 431 component_instance.offset_v_ratio = value 432 433 def get_component_instance_value(self, component_instance): 434 return component_instance._offset_v_ratio 435 436 def get_slider_hint(self, *args): 437 return GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/VerticalOffsetHint', amount=self.get_readable_value()) 438 439 def value_to_readable_value(self, value): 440 return value * 100.0 441 442 def readable_value_to_value(self, readable_value): 443 return readable_value / 100.0 444 445 446 class OrbitButton(ToggleButtonGroupButton): 447 448 def LoadTooltipPanel(self, tooltipPanel, *args): 449 tooltipPanel.columns = 2 450 tooltipPanel.LoadStandardSpacing() 451 tooltipPanel.AddLabelLarge(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/OrbitalPanelButton')) 452 tooltipPanel.AddDivider() 453 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/OrbitPatternProjector'), 'CTRL + Left Mouse') 454 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/PrecisionMode'), '+ SHIFT') 455 456 457 class OffsetButton(ToggleButtonGroupButton): 458 459 def LoadTooltipPanel(self, tooltipPanel, *args): 460 tooltipPanel.columns = 2 461 tooltipPanel.LoadStandardSpacing() 462 tooltipPanel.AddLabelLarge(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/OffsetPanelButton')) 463 tooltipPanel.AddDivider() 464 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/OffsetPatternProjector'), 'CTRL + Right Mouse') 465 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/PrecisionMode'), '+ SHIFT') 466 467 468 class RotateAndScaleButton(ToggleButtonGroupButton): 469 470 def LoadTooltipPanel(self, tooltipPanel, *args): 471 tooltipPanel.columns = 2 472 tooltipPanel.LoadStandardSpacing() 473 tooltipPanel.AddLabelLarge(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/RotateScalePanelButton')) 474 tooltipPanel.AddDivider() 475 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/RotatePatternProjector'), 'CTRL + Mouse Wheel') 476 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/ScalePattern'), 'CTRL + Left Mouse + Right Mouse') 477 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/RotatePatternSnap', angle=ROLL_SNAP_STEP), '+ ALT') 478 tooltipPanel.AddLabelShortcut(GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Gizmo/PrecisionMode'), '+ SHIFT')