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

studioDesignerPage.py (24673B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\cosmetics\ship\pages\studio\studioDesignerPage.py
      2 import eveicon
      3 from carbonui import Align, uiconst, AxisAlignment, Density, ButtonVariant, PickState
      4 from carbonui.button.const import HEIGHT_EXPANDED
      5 from carbonui.button.group import ButtonGroup, ButtonSizeMode, OverflowAlign
      6 from carbonui.control.button import Button
      7 from carbonui.primitives.container import Container
      8 from carbonui.primitives.containerAutoSize import ContainerAutoSize
      9 from carbonui.uianimations import animations
     10 from cosmetics.client.shipSkinDesignSvc import get_ship_skin_design_svc
     11 from cosmetics.client.shipSkinSequencingSvc import get_ship_skin_sequencing_svc
     12 from cosmetics.client.ships.qa.settings import should_show_popup_if_skin_name_missing
     13 from cosmetics.client.ships.skins.live_data import current_skin_design, current_skin_design_signals
     14 from cosmetics.common.ships.skins.static_data.slot import SlotsDataLoader
     15 from cosmetics.common.ships.skins.static_data.slot_configuration import SlotConfigurationsDataLoader, is_skinnable_ship
     16 from cosmetics.common.ships.skins.static_data.slot_name import SlotID, PATTERN_RELATED_SLOT_IDS
     17 from eve.client.script.ui.cosmetics.ship.const import ANIM_DURATION_LONG
     18 from eve.client.script.ui.cosmetics.ship.controls.characterInfo import DesignOwnerCharacterInfo
     19 from eve.client.script.ui.cosmetics.ship.controls.costInfo import StudioCostInfo
     20 from eve.client.script.ui.cosmetics.ship.controls.sequencingTime import SequencingTime
     21 from eve.client.script.ui.cosmetics.ship.pages.studio import studioSignals
     22 from eve.client.script.ui.cosmetics.ship.pages.studio.componentPanel import ComponentPanel
     23 from eve.client.script.ui.cosmetics.ship.pages.studio.cosmeticSlot import CosmeticSlot
     24 from eve.client.script.ui.cosmetics.ship.pages.studio.gizmoControls import GizmoControls
     25 from eve.client.script.ui.cosmetics.ship.pages.studio.hullSelectionPanel import HullSelectionPanel
     26 from eve.client.script.ui.cosmetics.ship.pages.studio.nebulaSelector import NebulaSelector
     27 from eve.client.script.ui.cosmetics.ship.pages.studio.nebulaBackground import SkinrBackgroundContainer
     28 from eve.client.script.ui.cosmetics.ship.pages.studio.patternSlots import PatternSlots
     29 from eve.client.script.ui.cosmetics.ship.pages.studio.radiallyAlignedMixin import RadiallyAlignedMixin
     30 from eve.client.script.ui.cosmetics.ship.pages.studio.randomizeControls import RandomizeControls
     31 from eve.client.script.ui.cosmetics.ship.pages.studio.saveSkinButton import SaveSkinButton, SaveAsSkinButton
     32 from eve.client.script.ui.cosmetics.ship.pages.studio.skinDesignDragData import SkinDesignDragData
     33 from eve.client.script.ui.cosmetics.ship.pages.studio.skinNameDialogue import SkinNameDialogue
     34 from eve.client.script.ui.cosmetics.ship.pages.studio.studioUtil import get_circular_layout_radius
     35 from eve.client.script.ui.shared.cloneGrade import ORIGIN_SHIP_SKINR
     36 from eve.client.script.ui.shared.cloneGrade.omegaTooltipPanelCell import OmegaTooltipPanelCell
     37 from characterskills.client import skill_signals
     38 from itertoolsext.Enum import Enum
     39 from localization import GetByLabel
     40 from signals import Signal
     41 PANEL_PAD_TOP = 160
     42 PANEL_PAD_BOTTOM = 160
     43 
     44 class StudioDesignerPage(Container):
     45     SLOT_ANGLE_GAP = 14
     46     SLOT_WINDOW_GAP = 40
     47     SLOT_ANGLES = {SlotID.PRIMARY_NANOCOATING: 180 + SLOT_ANGLE_GAP * 1.5,
     48      SlotID.SECONDARY_NANOCOATING: 180 + SLOT_ANGLE_GAP * 0.5,
     49      SlotID.TERTIARY_NANOCOATING: 180 - SLOT_ANGLE_GAP * 0.5,
     50      SlotID.TECH_AREA: 180 - SLOT_ANGLE_GAP * 1.5}
     51 
     52     def __init__(self, *args, **kwargs):
     53         super(StudioDesignerPage, self).__init__(*args, **kwargs)
     54         self.slots_by_id = {}
     55         self.panels_by_id = {}
     56         self.hull_selection_panel = None
     57         self.nebula_selection_panel = None
     58         self.bg_fade_container = None
     59         self.is_loaded = False
     60         self.minimum_element_width = 246
     61         self.circular_layout_pos_offset = 72
     62         self.on_create_btn = Signal('on_create_btn')
     63 
     64     def LoadPanel(self, animate = True):
     65         if not self.is_loaded:
     66             self.is_loaded = True
     67             self.construct_layout()
     68             self.connect_signals()
     69         self.populate_from_design()
     70         self.create_button.update()
     71         self.state = uiconst.UI_PICKCHILDREN
     72         animations.FadeTo(self, 0.0, 1.0, duration=0.6, timeOffset=ANIM_DURATION_LONG)
     73         self.deselect_all_cosmetic_slots()
     74         self.check_show_hull_selection_panel()
     75         self.update_circular_layout(*self.GetAbsoluteSize())
     76 
     77     def check_show_hull_selection_panel(self):
     78         active_ship_type_id = current_skin_design.get_active_ship_type_id()
     79         if not is_skinnable_ship(active_ship_type_id):
     80             self.show_hull_selection_panel()
     81         else:
     82             self.close_hull_selection_panel()
     83 
     84     def UnloadPanel(self, animate = True):
     85         self.Disable()
     86         animations.FadeTo(self, self.opacity, 0.0, duration=0.2, callback=self.Hide)
     87 
     88     def Close(self):
     89         try:
     90             self.disconnect_signals()
     91         finally:
     92             super(StudioDesignerPage, self).Close()
     93 
     94     def connect_signals(self):
     95         self.on_size_changed.connect(self.on_size_changed_signal)
     96         studioSignals.on_scene_zoom.connect(self.on_scene_zoom)
     97         current_skin_design_signals.on_slot_selected.connect(self.on_slot_selected)
     98         current_skin_design_signals.on_slot_fitting_changed.connect(self.on_slot_fitting_changed)
     99         current_skin_design_signals.on_design_reset.connect(self.on_design_reset)
    100         current_skin_design_signals.on_existing_design_loaded.connect(self.on_existing_design_loaded)
    101         current_skin_design_signals.on_ship_type_id_changed.connect(self.on_ship_type_id_changed)
    102         skill_signals.on_skill_levels_trained.connect(self.on_skill_levels_trained)
    103 
    104     def disconnect_signals(self):
    105         self.on_size_changed.disconnect(self.on_size_changed_signal)
    106         studioSignals.on_scene_zoom.disconnect(self.on_scene_zoom)
    107         current_skin_design_signals.on_slot_selected.disconnect(self.on_slot_selected)
    108         current_skin_design_signals.on_slot_fitting_changed.disconnect(self.on_slot_fitting_changed)
    109         current_skin_design_signals.on_design_reset.disconnect(self.on_design_reset)
    110         current_skin_design_signals.on_existing_design_loaded.disconnect(self.on_existing_design_loaded)
    111         current_skin_design_signals.on_ship_type_id_changed.disconnect(self.on_ship_type_id_changed)
    112         skill_signals.on_skill_levels_trained.disconnect(self.on_skill_levels_trained)
    113 
    114     def on_ship_type_id_changed(self, type_id):
    115         self.create_button.update()
    116         self.update_slots()
    117 
    118     def on_slot_fitting_changed(self, slot_id, component_instance):
    119         self.create_button.update()
    120         self.hide_all_slot_pulse_highlights()
    121         if slot_id == SlotID.PATTERN and component_instance is None:
    122             if current_skin_design.get_selected_slot_id() in (SlotID.PATTERN_MATERIAL, SlotID.SECONDARY_PATTERN, SlotID.SECONDARY_PATTERN_MATERIAL):
    123                 current_skin_design.set_selected_slot_id(None)
    124 
    125     def hide_all_slot_pulse_highlights(self):
    126         for slot_id in self.slots_by_id.keys():
    127             slot = self.slots_by_id[slot_id]
    128             slot.hide_pulse_highlight()
    129 
    130     def on_skill_levels_trained(self, levels_trained):
    131         self.sequencing_time.update()
    132 
    133     def construct_layout(self):
    134         self.construct_base_containers()
    135         self.construct_gizmo_controls()
    136         self.construct_bottom_left_buttons()
    137         self.construct_bottom_right_buttons()
    138         self.reconstruct_slots_and_panels()
    139         self.construct_randomize_controls()
    140         self.construct_undo_buttons()
    141         self.construct_pattern_slots()
    142         self.construct_creator_character_info()
    143         self.construct_background_ui()
    144 
    145     def construct_undo_buttons(self):
    146         self.undo_btns = UndoButtons(parent=self.center_right_cont)
    147 
    148     def construct_randomize_controls(self):
    149         self.randomize_controls = RandomizeControls(name='randomize_controls', parent=self.center_left_cont)
    150         self.randomize_controls.angle_deg = 220
    151 
    152     def construct_background_ui(self):
    153         self.nebula_selection_panel = NebulaSelector(name='nebula_selector', parent=self, align=Align.TORIGHT_NOPUSH, padding=(0,
    154          PANEL_PAD_TOP,
    155          32,
    156          PANEL_PAD_BOTTOM), clipChildren=True)
    157         self.bg_fade_container = SkinrBackgroundContainer(name='nebula_fade_container', parent=self, align=Align.TOALL, pickState=uiconst.TR2_SPS_OFF)
    158 
    159     def construct_bottom_left_buttons(self):
    160         button_group = ButtonGroup(parent=self.button_overlay_cont, align=Align.TOLEFT, button_alignment=AxisAlignment.START, density=Density.EXPANDED, button_size_mode=ButtonSizeMode.DYNAMIC, ignore_overflow=True)
    161         self.hull_selection_button = Button(parent=self, align=Align.CENTER, label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/HullSelection'), func=self.on_hull_selection_button, texturePath=eveicon.spaceship_command)
    162         button_group.add_button(self.hull_selection_button)
    163         self.save_skin_button = SaveSkinButton(label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/Save'))
    164         button_group.add_button(self.save_skin_button)
    165         self.save_as_skin_button = SaveAsSkinButton(label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/SaveAs'))
    166         button_group.add_button(self.save_as_skin_button)
    167 
    168     def construct_creator_character_info(self):
    169         self.made_by_character_info = DesignOwnerCharacterInfo(name='made_by_character_info', parent=self, align=Align.BOTTOMLEFT, left=32, top=116)
    170         self._update_creator_character_info()
    171 
    172     def _update_creator_character_info(self):
    173         creator_char_id = current_skin_design.get().creator_character_id
    174         self.made_by_character_info.character_id = creator_char_id
    175         self.made_by_character_info.display = creator_char_id != session.charid
    176 
    177     def get_share_button_drag_data(self, *args):
    178         saved_design_id = current_skin_design.get().saved_skin_design_id
    179         if saved_design_id:
    180             saved_design_data = get_ship_skin_design_svc().get_saved_design(saved_design_id)
    181             if saved_design_data:
    182                 return SkinDesignDragData(design_data=saved_design_data, saved_design_id=saved_design_id)
    183 
    184     def construct_bottom_right_buttons(self):
    185         self.bottom_right_container = Container(name='bottom_right_container', parent=self.button_overlay_cont, align=Align.TORIGHT_PROP, width=0.3)
    186         button_group = ButtonGroup(parent=self.bottom_right_container, align=Align.BOTTOMRIGHT, button_alignment=AxisAlignment.END, button_size_mode=ButtonSizeMode.STRETCH, overflow_align=OverflowAlign.LEFT, density=Density.EXPANDED, width=220)
    187         self.create_button = CreateSKINButton(label=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/FinalizeDesign'), func=self._on_create_btn, variant=ButtonVariant.PRIMARY)
    188         info_container = ContainerAutoSize(name='info_container', parent=self, align=Align.BOTTOMRIGHT, width=button_group.width, left=64, top=108)
    189         StudioCostInfo(name='cost_info', parent=info_container, align=Align.TOBOTTOM)
    190         self.sequencing_time = SequencingTime(name='sequencing_time', parent=info_container, align=Align.TOBOTTOM, minWidth=button_group.width - 32, padBottom=4)
    191         button_group.add_button(self.create_button)
    192 
    193     def _on_create_btn(self, *args):
    194         if should_show_popup_if_skin_name_missing.is_enabled() and not current_skin_design.get().name:
    195             dialogue = SkinNameDialogue(window_name=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/SKINName'))
    196             form_value = dialogue.ShowModal()
    197             if form_value == uiconst.ID_CLOSE or not form_value[SkinNameDialogue.COMPONENT_NAME]:
    198                 return
    199         self.on_create_btn()
    200 
    201     def on_hull_selection_button(self, *args):
    202         if self.hull_selection_panel:
    203             self.close_hull_selection_panel()
    204         else:
    205             self.show_hull_selection_panel()
    206 
    207     def show_hull_selection_panel(self):
    208         self.deselect_all_cosmetic_slots()
    209         self.hull_selection_panel = HullSelectionPanel(parent=self.overlay_cont, align=uiconst.TOLEFT, width=360, padding=(0,
    210          PANEL_PAD_TOP,
    211          0,
    212          PANEL_PAD_BOTTOM))
    213         self.hull_selection_panel.on_close_btn.connect(self.on_hull_selection_close_btn)
    214         animations.FadeTo(self.hull_selection_panel, 0.0, 1.0, duration=0.2)
    215 
    216     def on_hull_selection_close_btn(self, *args):
    217         self.close_hull_selection_panel()
    218 
    219     def close_hull_selection_panel(self):
    220         if self.hull_selection_panel:
    221             self.hull_selection_panel.Close()
    222             self.hull_selection_panel = None
    223 
    224     def deselect_all_cosmetic_slots(self):
    225         current_skin_design.set_selected_slot_id(None)
    226 
    227     def close_all_overlay_panels(self):
    228         self.close_hull_selection_panel()
    229         self.deselect_all_cosmetic_slots()
    230 
    231     def construct_base_containers(self):
    232         self.button_overlay_cont = Container(name='button_overlay_cont', parent=self, padding=(32, 0, 64, 64), align=Align.TOBOTTOM_NOPUSH, height=HEIGHT_EXPANDED)
    233         self.center_top_cont = Container(name='center_top_cont', parent=self)
    234         self.center_left_cont = Container(name='center_left_cont', parent=self)
    235         self.center_right_cont = Container(name='center_right_cont', parent=self)
    236         self.slots_left_cont = Container(name='slots_left_cont', parent=self.center_left_cont)
    237         self.slots_right_cont = Container(name='slots_right_cont', parent=self.center_right_cont)
    238         self.overlay_cont = Container(name='overlay_cont', parent=self, padding=32)
    239 
    240     def construct_gizmo_controls(self):
    241         self.gizmo_controls = GizmoControls(name='gizmo_controls', parent=self.center_top_cont, align=Align.CENTERTOP)
    242 
    243     def reconstruct_slots_and_panels(self):
    244         self.reconstruct_slots()
    245         self.reconstruct_panels()
    246 
    247     def reconstruct_slots(self):
    248         self.construct_non_pattern_slots()
    249 
    250     def construct_non_pattern_slots(self):
    251         for slot in self.slots_by_id.values():
    252             slot.Close()
    253 
    254         self.slots_by_id.clear()
    255         slot_config = self.get_current_skin_slot_config()
    256         for slot_id in slot_config.slots:
    257             if slot_id in PATTERN_RELATED_SLOT_IDS:
    258                 continue
    259             slot_data = SlotsDataLoader.get_slot_data(slot_id)
    260             slot = CosmeticSlot(name='slot_{name}'.format(name=slot_data.internal_name), parent=self.slots_left_cont, slot_data=slot_data)
    261             slot.angle_deg = self.SLOT_ANGLES.get(slot_data.unique_id)
    262             self.slots_by_id[slot_data.unique_id] = slot
    263             if slot_id == SlotID.PRIMARY_NANOCOATING and not current_skin_design.get().has_fitted_components():
    264                 slot.show_pulse_highlight()
    265 
    266     def construct_pattern_slots(self):
    267         self.pattern_slots = PatternSlots(name='pattern_slots', parent=self.slots_right_cont, align=Align.CENTER)
    268 
    269     def reconstruct_panels(self):
    270         for panel in self.panels_by_id.values():
    271             panel.on_close_btn.disconnect(self.on_panel_close_btn)
    272             panel.Close()
    273 
    274         self.panels_by_id.clear()
    275         slot_config = self.get_current_skin_slot_config()
    276         for slot_id in slot_config.slots:
    277             slot_data = SlotsDataLoader.get_slot_data(slot_id)
    278             self._construct_panel(slot_data)
    279 
    280     def _construct_panel(self, slot_data):
    281         align = Align.TORIGHT_NOPUSH if slot_data.unique_id in PATTERN_RELATED_SLOT_IDS else Align.TOLEFT_NOPUSH
    282         panel = ComponentPanel(name='panel_{slot_name}'.format(slot_name=slot_data.internal_name), parent=self.overlay_cont, slot_data=slot_data, align=align, padding=(0,
    283          PANEL_PAD_TOP,
    284          0,
    285          PANEL_PAD_BOTTOM))
    286         panel.on_close_btn.connect(self.on_panel_close_btn)
    287         self.panels_by_id[slot_data.unique_id] = panel
    288 
    289     def on_panel_close_btn(self, *args):
    290         self.close_all_overlay_panels()
    291 
    292     def on_design_reset(self, *args):
    293         self.populate_from_design()
    294         self.update_slots()
    295 
    296     def on_existing_design_loaded(self, *args):
    297         self.populate_from_design()
    298         self.update_slots()
    299         self._update_creator_character_info()
    300 
    301     def populate_from_design(self):
    302         self.populate_non_pattern_slots_from_design()
    303         self.populate_pattern_slots_from_design()
    304         self.save_skin_button.update_button_state()
    305         self.save_as_skin_button.update_button_state()
    306 
    307     def populate_non_pattern_slots_from_design(self):
    308         for slot_id in self.slots_by_id.keys():
    309             component_instance = current_skin_design.get().slot_layout.fitted_slots.get(slot_id, None)
    310             self.slots_by_id[slot_id].on_slot_fitting_changed(slot_id, component_instance)
    311             self.panels_by_id[slot_id].select_component(component_instance)
    312 
    313     def populate_pattern_slots_from_design(self):
    314         self.pattern_slots.populate_from_design()
    315         for slot_id in self.pattern_slots.slots_by_id.keys():
    316             component_instance = current_skin_design.get().slot_layout.fitted_slots.get(slot_id, None)
    317             self.panels_by_id[slot_id].select_component(component_instance)
    318 
    319     def on_slot_selected(self, slot_id):
    320         self.close_hull_selection_panel()
    321         for panel in self.panels_by_id.values():
    322             panel.on_slot_selected(slot_id)
    323 
    324     def on_scene_zoom(self, is_zoomed):
    325         if is_zoomed:
    326             self.center_top_cont.state = uiconst.UI_DISABLED
    327             self.center_left_cont.state = uiconst.UI_DISABLED
    328             self.center_right_cont.state = uiconst.UI_DISABLED
    329         else:
    330             self.center_top_cont.state = uiconst.UI_PICKCHILDREN
    331             self.center_left_cont.state = uiconst.UI_PICKCHILDREN
    332             self.center_right_cont.state = uiconst.UI_PICKCHILDREN
    333         if is_zoomed:
    334             width_distance, height_distance = self.get_zoomed_width_height()
    335             left_target = -width_distance
    336             right_target = width_distance
    337             top_target = -(min(width_distance, height_distance) + self.SLOT_WINDOW_GAP)
    338         else:
    339             left_target = 0.0
    340             right_target = 0.0
    341             top_target = 0.0
    342         animations.MorphScalar(self.center_left_cont, 'left', self.center_left_cont.left, left_target, duration=0.3)
    343         animations.MorphScalar(self.center_right_cont, 'left', self.center_right_cont.left, right_target, duration=0.3)
    344         animations.MorphScalar(self.center_top_cont, 'top', self.center_top_cont.top, top_target, duration=0.3)
    345 
    346     def get_zoomed_width_height(self):
    347         diameter = self.get_circular_layout_radius() * 2.0
    348         page_width, page_height = self.GetAbsoluteSize()
    349         width_distance = page_width - diameter - self.SLOT_WINDOW_GAP * 2.0
    350         height_distance = page_height - diameter - self.SLOT_WINDOW_GAP * 2.0
    351         return (width_distance, height_distance)
    352 
    353     def on_size_changed_signal(self, width, height):
    354         self.update_circular_layout(width, height)
    355         if width < 1100:
    356             self.gizmo_controls.width = self.gizmo_controls.default_width + width - 1100
    357         else:
    358             self.gizmo_controls.width = self.gizmo_controls.default_width
    359 
    360     def update_circular_layout(self, width, height):
    361         radius = self.get_circular_layout_radius()
    362         width_available = min(380, width / 2 - radius - self.circular_layout_pos_offset)
    363         compact_mode = width_available < self.minimum_element_width
    364         for slot in self.slots_by_id.values():
    365             slot.radius = radius
    366 
    367         self.pattern_slots.update_radial_position(radius + 40)
    368         for panel in self.panels_by_id.values():
    369             self.move_panel(compact_mode, width_available, panel)
    370 
    371         self.move_panel(compact_mode, width_available, self.nebula_selection_panel)
    372         width_available_no_min = width / 2 - radius - self.circular_layout_pos_offset
    373         height_available = height / 2 - radius - self.circular_layout_pos_offset
    374         self.bg_fade_container.adjust_size(width_available_no_min, height_available)
    375         self.gizmo_controls.radius = radius
    376         self.randomize_controls.radius = radius - 20
    377         self.randomize_controls.angle_deg = 212 if compact_mode else 220
    378         if compact_mode:
    379             self.undo_btns.align = Align.BOTTOMRIGHT
    380             self.undo_btns.left = 64
    381             self.undo_btns.top = 210
    382         else:
    383             self.undo_btns.align = Align.CENTER
    384             self.undo_btns.radius = radius
    385             self.undo_btns.angle_deg = 45
    386 
    387     def move_panel(self, compact_mode, width_available, panel):
    388         if compact_mode:
    389             offset = 128 if panel.align == Align.TOLEFT else 164
    390             panel.left = width_available + offset
    391             panel.width = self.minimum_element_width
    392             panel.padTop = PANEL_PAD_TOP + 16
    393             panel.padBottom = PANEL_PAD_BOTTOM + 8
    394         else:
    395             panel.left = 0
    396             panel.width = width_available
    397             panel.padTop = PANEL_PAD_TOP
    398             panel.padBottom = PANEL_PAD_BOTTOM
    399 
    400     def update_slots(self):
    401         self.reconstruct_slots_and_panels()
    402         self.populate_from_design()
    403         self.update_circular_layout(*self.GetAbsoluteSize())
    404 
    405     def get_circular_layout_radius(self):
    406         return get_circular_layout_radius(*self.GetAbsoluteSize())
    407 
    408     def get_current_skin_slot_config(self):
    409         return SlotConfigurationsDataLoader.get_for_ship(current_skin_design.get().ship_type_id)
    410 
    411 
    412 @Enum
    413 
    414 class CreateButtonErrorState(object):
    415     OMEGA_BLOCKED = 1
    416     NO_COMPONENT_SELECTED = 2
    417     SEQUENCING_FEATURE_DISABLED = 3
    418 
    419 
    420 class CreateSKINButton(Button):
    421     __notifyevents__ = ['OnSubscriptionChanged']
    422     error_state = None
    423 
    424     def __init__(self, get_menu_entry_data_func = None, **kwargs):
    425         super(CreateSKINButton, self).__init__(get_menu_entry_data_func, **kwargs)
    426         sm.RegisterNotify(self)
    427 
    428     def OnSubscriptionChanged(self):
    429         self.update()
    430 
    431     def LoadTooltipPanel(self, tooltipPanel, *args):
    432         if self.error_state == CreateButtonErrorState.OMEGA_BLOCKED:
    433             tooltipPanel.LoadStandardSpacing()
    434             tooltipPanel.columns = 2
    435             tooltipPanel.AddRow(rowClass=OmegaTooltipPanelCell, text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/CannotSequenceForHullOmega'), origin=ORIGIN_SHIP_SKINR)
    436             tooltipPanel.pickState = PickState.ON
    437         elif self.error_state == CreateButtonErrorState.NO_COMPONENT_SELECTED:
    438             tooltipPanel.LoadStandardSpacing()
    439             tooltipPanel.columns = 1
    440             tooltipPanel.AddLabelMedium(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/NoComponentsSelected'), wrapWidth=300)
    441         elif self.error_state == CreateButtonErrorState.SEQUENCING_FEATURE_DISABLED:
    442             tooltipPanel.LoadStandardSpacing()
    443             tooltipPanel.columns = 1
    444             tooltipPanel.AddLabelMedium(text=GetByLabel('UI/Personalization/ShipSkins/SKINR/Studio/SequencingFeatureDisabled'), wrapWidth=300)
    445 
    446     def update(self):
    447         if not get_ship_skin_sequencing_svc().can_sequence_for_hull(current_skin_design.get().ship_type_id):
    448             self.enabled = False
    449             self.error_state = CreateButtonErrorState.OMEGA_BLOCKED
    450         elif not current_skin_design.get().slot_layout.fitted_slots:
    451             self.enabled = False
    452             self.error_state = CreateButtonErrorState.NO_COMPONENT_SELECTED
    453         else:
    454             self.enabled = True
    455             self.error_state = None
    456 
    457 
    458 class UndoButtons(ButtonGroup, RadiallyAlignedMixin):
    459     default_align = Align.CENTER
    460 
    461     def __init__(self, **kw):
    462         super(UndoButtons, self).__init__(**kw)
    463         self.undo_btn = Button(parent=self, func=self.on_undo_btn, texturePath=eveicon.arrow_rotate_left, hint=GetByLabel('UI/Common/Undo'), variant=ButtonVariant.GHOST)
    464         self.redo_btn = Button(parent=self, func=self.on_redo_btn, texturePath=eveicon.arrow_rotate_right, hint=GetByLabel('UI/Common/Redo'), variant=ButtonVariant.GHOST)
    465         self.connnect_signals()
    466         self.update()
    467 
    468     def connnect_signals(self):
    469         current_skin_design_signals.on_undo_history_change.connect(self.on_undo_history_change)
    470 
    471     def on_undo_history_change(self):
    472         self.update()
    473 
    474     def update(self):
    475         self.undo_btn.enabled = current_skin_design.is_undo_enabled()
    476         self.redo_btn.enabled = current_skin_design.is_redo_enabled()
    477 
    478     def on_undo_btn(self, *args):
    479         current_skin_design.undo()
    480 
    481     def on_redo_btn(self, *args):
    482         current_skin_design.redo()