NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

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')