---------------------------------------------------- -- SamUI v0.3.3 -- Eine vollständige UI-Alternative -- WoW Classic Anniversary TBC -- Interface 20506 -- Build 69110 ---------------------------------------------------- local _, ns = ... local module = ns:new_module("installer") local navigation_definitions = { { kind = "item", key = "about", label = "About", content_key = "about" }, { kind = "category", key = "unitframes", label = "Unitframes", children = { { key = "unitframes_player", label = "Spieler", content_key = "unitframes_player" }, { key = "unitframes_target", label = "Ziel", content_key = "unitframes_target" }, { key = "unitframes_pet", label = "Begleiter", content_key = "unitframes_pet" }, { key = "unitframes_targettarget", label = "Ziel von Ziel", content_key = "unitframes_targettarget" }, }, }, { kind = "category", key = "leisten", label = "Leisten", children = { { key = "leisten_allgemein", label = "Allgemein", content_key = "leisten_allgemein" }, { key = "leisten_leiste1", label = "Leiste 1", content_key = "leisten_leiste1" }, { key = "leisten_leiste2", label = "Leiste 2", content_key = "leisten_leiste2" }, { key = "leisten_leiste3", label = "Leiste 3", content_key = "leisten_leiste3" }, { key = "leisten_leiste4", label = "Leiste 4", content_key = "leisten_leiste4" }, { key = "leisten_leiste5", label = "Leiste 5", content_key = "leisten_leiste5" }, { key = "leisten_leiste6", label = "Leiste 6", content_key = "leisten_leiste6" }, { key = "leisten_leiste7", label = "Leiste 7", content_key = "leisten_leiste7" }, { key = "leisten_leiste8", label = "Leiste 8", content_key = "leisten_leiste8" }, { key = "leisten_leiste9", label = "Leiste 9", content_key = "leisten_leiste9" }, { key = "leisten_leiste10", label = "Leiste 10", content_key = "leisten_leiste10" }, { key = "leisten_pet", label = "Begleiter", content_key = "leisten_pet" }, { key = "leisten_stance", label = "Haltungsleiste", content_key = "leisten_stance" }, }, }, { kind = "category", key = "plaketten", label = "Plaketten", children = { { key = "plaketten_allgemein", label = "Allgemein", content_key = "plaketten" }, }, }, { kind = "category", key = "taschen", label = "Taschen", children = {} }, { kind = "category", key = "ui_elements", label = "UI-Elemente", children = { { key = "ui_elements_minimap", label = "Minimap", content_key = "minimap" }, { key = "ui_elements_tooltip", label = "Tooltip", content_key = "tooltip" }, { key = "ui_elements_lootfenster", label = "Loot und Beute", content_key = "lootfenster" }, { key = "ui_elements_chat", label = "Chat", content_key = "chat" }, }, }, { kind = "category", key = "verbesserungen", label = "Verbesserungen", children = { { key = "verbesserungen_allgemein", label = "Allgemein", content_key = "verbesserungen" }, { key = "verbesserungen_zusatzleisten", label = "Zusatzleisten", content_key = "zusatzleisten" }, }, }, { kind = "category", key = "profile", label = "System", children = { { key = "system_blizzard", label = "Blizzard", content_key = "system_blizzard" }, { key = "system_samui_elements", label = "SamUI", content_key = "system_samui_elements" }, { key = "profile_management", label = "Profile", content_key = "profile" }, }, }, } local unitframe_option_order = { "player", "target", "pet", "targettarget", } local unitframe_option_definitions = { player = { label = "Spieler", width = { minimum = 200, maximum = 400 }, height = { minimum = 30, maximum = 100 }, }, target = { label = "Ziel", width = { minimum = 200, maximum = 400 }, height = { minimum = 30, maximum = 100 }, }, pet = { label = "Begleiter", width = { minimum = 100, maximum = 200 }, height = { minimum = 20, maximum = 70 }, }, targettarget = { label = "Ziel von Ziel", width = { minimum = 100, maximum = 200 }, height = { minimum = 20, maximum = 70 }, }, } local unitframe_row_definitions = { { key = "width", label = "Breite", step = 5 }, { key = "height", label = "Höhe", step = 5 }, { key = "offset_x", label = "Position X", step = 10 }, { key = "offset_y", label = "Position Y", step = 10 }, } local unitframe_position_range = { minimum = -1000, maximum = 1000, } local unitframe_position_y_range = { minimum = -500, maximum = 500, } local actionbar_option_order = { "leiste1", "leiste2", "leiste3", "leiste4", "leiste5", "leiste6", "leiste7", "leiste8", "leiste9", "leiste10", "pet", "stance", } local actionbar_general_lock_options = { { value = "ALT", label = "ALT-Taste" }, { value = "CTRL", label = "STRG-Taste" }, { value = "SHIFT", label = "Umschalttaste" }, { value = "NONE", label = "keine" }, } local actionbar_general_cooldown_options = { { value = true, label = "Aktiviert" }, { value = false, label = "Deaktiviert" }, } local actionbar_option_definitions = { leiste1 = { label = "Leiste 1", numbered = true }, leiste2 = { label = "Leiste 2", numbered = true }, leiste3 = { label = "Leiste 3", numbered = true }, leiste4 = { label = "Leiste 4", numbered = true }, leiste5 = { label = "Leiste 5", numbered = true }, leiste6 = { label = "Leiste 6", numbered = true }, leiste7 = { label = "Leiste 7", numbered = true }, leiste8 = { label = "Leiste 8", numbered = true }, leiste9 = { label = "Leiste 9", numbered = true }, leiste10 = { label = "Leiste 10", numbered = true }, pet = { label = "Begleiter", numbered = false }, stance = { label = "Haltungsleiste", numbered = false }, } local actionbar_count_slider_definition = { key = "button_count", label = "Anzahl Symbole", minimum = 1, maximum = 12, step = 1 } local actionbar_buttons_per_row_slider_definition = { key = "buttons_per_row", label = "Symbole pro Reihe", minimum = 1, maximum = 12, step = 1 } local actionbar_common_slider_definitions = { { key = "spacing", label = "Abstand Symbole", minimum = 0, maximum = 20, step = 1 }, { key = "scale", label = "Skalierung", minimum = 50, maximum = 150, step = 1, suffix = "%" }, { key = "offset_x", label = "Position X", minimum = -1000, maximum = 1000, step = 10 }, { key = "offset_y", label = "Position Y", minimum = -500, maximum = 500, step = 10 }, } local minimap_option_order = { "zoom_buttons", "zone_text", "mail_icon", "clock", "clock_24h", "group_finder_icon", "calendar_icon", } local minimap_option_definitions = { zoom_buttons = { label = "Zoom-Buttons anzeigen", default = false }, zone_text = { label = "Zonentext anzeigen", default = false }, mail_icon = { label = "Postsymbol anzeigen", default = true }, clock = { label = "Uhr anzeigen", default = true }, clock_24h = { label = "24-Stunden-Uhr", default = true }, group_finder_icon = { label = "Gruppensuche-Symbol anzeigen", default = true }, calendar_icon = { label = "Kalendersymbol anzeigen", default = false }, } local chat_option_order = { "buttons_hidden", "free_movement", "message_fading", } local chat_option_definitions = { buttons_hidden = { label = "Buttons ausblenden", default = true }, free_movement = { label = "Chat frei bewegen", default = true }, message_fading = { label = "Nachrichten-Fading", default = true }, } local verbesserungen_option_order = { "fast_loot_enabled", "auto_repair_mode", "auto_sell_enabled", } local verbesserungen_boolean_definitions = { fast_loot_enabled = { label = "Schnelleres Looten", default = false }, auto_sell_enabled = { label = "Automatisch verkaufen", default = true }, } local totem_bar_option_definition = { key = "totem_bar_enabled", label = "Totemleiste aktivieren", default = true, } local auto_repair_options = { { value = "disabled", label = "Deaktiviert" }, { value = "player", label = "Aktiviert" }, { value = "guild", label = "Aktiviert auf Gildenbank" }, } local zusatzleisten_option_order = { "experience_enabled", "reputation_enabled", "system_enabled", } local zusatzleisten_option_definitions = { experience_enabled = { label = "Erfahrungsleiste anzeigen", default = true }, reputation_enabled = { label = "Rufleiste anzeigen", default = true }, system_enabled = { label = "Systemleiste anzeigen", default = true }, } local tooltip_option_order = { "player_name_colored", "guild_name_shown", } local tooltip_option_definitions = { player_name_colored = { label = "Spielername farbig", default = true }, guild_name_shown = { label = "Gildenname anzeigen", default = true }, } local lootfenster_slider_definition = { key = "size", label = "Größe des Lootfensters", minimum = 50, maximum = 150, step = 10, default = 100, } local loot_summary_boolean_definitions = { summary_enabled = { label = "Loot-Summary aktivieren", default = true }, summary_gold_enabled = { label = "Gold anzeigen", default = true }, } local loot_summary_quality_options = { { value = 0, label = "Schlecht" }, { value = 1, label = "Gewöhnlich" }, { value = 2, label = "Ungewöhnlich" }, { value = 3, label = "Selten" }, } local loot_summary_duration_options = { { value = 2, label = "2 Sekunden" }, { value = 4, label = "4 Sekunden" }, { value = 6, label = "6 Sekunden" }, { value = 8, label = "8 Sekunden" }, } local nameplate_slider_definition = { key = "scale", label = "Skalierung", minimum = 50, maximum = 150, step = 10, default = 100, } local nameplate_boolean_order = { "threat_enabled", "blizzard_nameplates_shown", "target_highlight_enabled", "health_smoothing_enabled", } local nameplate_boolean_definitions = { threat_enabled = { label = "Bedrohungsanzeige", default = true }, blizzard_nameplates_shown = { label = "Blizzard-Plaketten", default = false }, target_highlight_enabled = { label = "Ziel hervorheben", default = true }, health_smoothing_enabled = { label = "Smooth Health", default = true }, } local blizzard_option_order = { "unitframes_shown", "groupframes_shown", "raidframes_shown", "arenaframes_shown", "actionbars_shown", } local blizzard_visibility_order = { "unitframes_shown", "groupframes_shown", "raidframes_shown", "arenaframes_shown", "actionbars_shown", "nameplates_shown", } local blizzard_option_definitions = { unitframes_shown = { label = "Blizzard Unitframes", default = false }, groupframes_shown = { label = "Blizzard Gruppenframes", default = true }, raidframes_shown = { label = "Blizzard Raidframes", default = true }, arenaframes_shown = { label = "Blizzard Arenaframes", default = true }, actionbars_shown = { label = "Blizzard Aktionsleisten", default = false }, } local blizzard_visibility_state = { unitframes_shown = false, groupframes_shown = true, raidframes_shown = true, arenaframes_shown = true, actionbars_shown = false, nameplates_shown = false, } local blizzard_hidden_frames = setmetatable({}, { __mode = "k" }) local blizzard_hooked_frames = setmetatable({}, { __mode = "k" }) local blizzard_parent_detach_frames = setmetatable({}, { __mode = "k" }) local blizzard_event_detach_frames = setmetatable({}, { __mode = "k" }) local blizzard_partial_event_detach_frames = setmetatable({}, { __mode = "k" }) local blizzard_event_detached_frames = setmetatable({}, { __mode = "k" }) local blizzard_action_button_detached_frames = setmetatable({}, { __mode = "k" }) local blizzard_hidden_parent = nil local blizzard_visibility_pending = false local blizzard_profile_applied = false local blizzard_missing_frame_warnings = {} local blizzard_visibility_addons = { Blizzard_ActionBar = true, Blizzard_ActionBarController = true, Blizzard_CompactRaidFrames = true, Blizzard_MainMenuBarBagButtons = true, Blizzard_MicroMenu = true, Blizzard_RaidFrame = true, Blizzard_UIPanels_Game = true, Blizzard_UnitFrame = true, } local actionbar_row_width = 430 local actionbar_slider_row_height = 48 local actionbar_dropdown_row_height = 32 local actionbar_row_gap = 4 local actionbar_control_label_width = 150 local actionbar_control_gap = 12 local actionbar_control_width = 220 local actionbar_dropdown_width = 160 local profile_scroll_child_minimum_height = 760 local profile_row_height = 28 local profile_button_width = 70 local profile_button_height = 22 local profile_element_gap = 4 local navigation_width = 170 local navigation_row_height = 20 local navigation_child_indent = 16 local slider_counter = 0 local native_button_counter = 0 local native_edit_box_counter = 0 local native_close_button_counter = 0 local function apply_samui_font(font_region, font_object) if not font_region or not font_object or type(font_region.SetFontObject) ~= "function" then return end font_region:SetFontObject(font_object) end local function apply_minimal_slider_fonts(slider_container) if not slider_container then return end apply_samui_font(slider_container.TopText, samui_font_12) apply_samui_font(slider_container.MinText, samui_font_10) apply_samui_font(slider_container.MaxText, samui_font_10) end local function deep_copy(value, seen) if type(value) ~= "table" then return value end seen = seen or {} if seen[value] then return seen[value] end local copy = {} seen[value] = copy for key, entry in pairs(value) do copy[deep_copy(key, seen)] = deep_copy(entry, seen) end return copy end local function create_panel(parent, surface_name, palette, designelemente) local panel = CreateFrame("Frame", nil, parent) local background = panel:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(panel) background:SetColorTexture(palette:get_surface(surface_name)) local border = designelemente:create_border_overlay(panel, parent, "frame") return panel, border end local function apply_standard_button_font(button, font_object) if not button or not font_object then return nil end button:SetNormalFontObject(font_object) button:SetHighlightFontObject(font_object) button:SetDisabledFontObject(font_object) local label = button:GetFontString() if label then label:SetFontObject(font_object) label:SetWordWrap(false) label:SetJustifyH("CENTER") label:SetJustifyV("MIDDLE") end return label end local function create_standard_button(parent, width, text) native_button_counter = native_button_counter + 1 local button_name = "samui_options_button_" .. native_button_counter local button = CreateFrame("Button", button_name, parent, "UIPanelButtonTemplate") button:SetHeight(22) local label = apply_standard_button_font(button, samui_font_12) button:SetText(text or "") local label_width = label and label:GetStringWidth() or 0 button:SetWidth(math.max(width or 22, label_width + 24, 22)) return { frame = button, label = label, } end local function create_edit_box(parent, width, height, multiline) native_edit_box_counter = native_edit_box_counter + 1 local edit_box_name = "samui_options_edit_box_" .. native_edit_box_counter local edit_box = CreateFrame("EditBox", edit_box_name, parent, "InputBoxTemplate") edit_box:SetSize(width, height) edit_box:SetAutoFocus(false) edit_box:SetMultiLine(multiline == true) edit_box:SetJustifyH("LEFT") apply_samui_font(edit_box, samui_font_12) if multiline then edit_box:SetJustifyV("TOP") end return edit_box end local function create_settings_page_header(parent, title_text, palette, designelemente) local heading = parent:CreateFontString(nil, "OVERLAY") heading:SetFontObject(samui_font_14) heading:SetPoint("TOPLEFT", parent, "TOPLEFT", 12, -12) heading:SetText(title_text) heading:SetTextColor(palette:get_rgb("gold")) local divider = parent:CreateTexture(nil, "ARTWORK") divider:SetAtlas("Options_HorizontalDivider", true) divider:SetWidth(math.max(parent:GetWidth() * 0.90, 1)) divider:SetPoint("TOP", parent, "TOP", 0, -38) return heading, divider end local function create_minimal_scroll_frame(parent, scroll_frame_name, scroll_bar_name) local scroll_frame = CreateFrame("ScrollFrame", scroll_frame_name, parent) local scroll_bar = CreateFrame( "EventFrame", scroll_bar_name, parent, "MinimalScrollBar" ) scroll_frame:EnableMouseWheel(true) scroll_bar:SetHideIfUnscrollable(true) ScrollUtil.InitScrollFrameWithScrollBar(scroll_frame, scroll_bar) return scroll_frame, scroll_bar end local function clamp_value(value, minimum, maximum) if value < minimum then return minimum elseif value > maximum then return maximum end return value end local function round_to_step(value, step) if value >= 0 then return math.floor(value / step + 0.5) * step end return math.ceil(value / step - 0.5) * step end local function get_slider_range(unit_definition, key) if key == "width" then return unit_definition.width.minimum, unit_definition.width.maximum elseif key == "height" then return unit_definition.height.minimum, unit_definition.height.maximum elseif key == "offset_x" then return unitframe_position_range.minimum, unitframe_position_range.maximum end return unitframe_position_y_range.minimum, unitframe_position_y_range.maximum end local function collect_blizzard_profile_data() local data = {} for index = 1, #blizzard_option_order do local key = blizzard_option_order[index] data[key] = blizzard_visibility_state[key] == true end return data end local function add_resolved_frame(group, frame, frame_name, report_missing, detach_parent) if frame then group[#group + 1] = frame if detach_parent then blizzard_parent_detach_frames[frame] = true end return end if report_missing and not blizzard_missing_frame_warnings[frame_name] then blizzard_missing_frame_warnings[frame_name] = true ns:chat_message("Blizzard-Frame '" .. frame_name .. "' ist nicht verfügbar", true) end end local function add_resolved_actionbar_frame( group, frame, frame_name, report_missing, unregister_all_events, partial_events ) add_resolved_frame(group, frame, frame_name, report_missing) if not frame then return end blizzard_parent_detach_frames[frame] = true if unregister_all_events then blizzard_event_detach_frames[frame] = true end if partial_events then blizzard_partial_event_detach_frames[frame] = partial_events end end local function add_resolved_actionbar_buttons(group, prefix, report_missing) local missing = false for index = 1, 12 do local button = _G[prefix .. index] if button then group[#group + 1] = button else missing = true end end if missing and report_missing and not blizzard_missing_frame_warnings[prefix] then blizzard_missing_frame_warnings[prefix] = true ns:chat_message("Blizzard-Buttons '" .. prefix .. "1-12' sind nicht vollständig verfügbar", true) end end local main_menu_bar_visibility_events = { "PLAYER_REGEN_ENABLED", "PLAYER_REGEN_DISABLED", "ACTIONBAR_SHOWGRID", "ACTIONBAR_HIDEGRID", } local function resolve_blizzard_frames(report_missing) local groups = { unitframes_shown = {}, groupframes_shown = {}, raidframes_shown = {}, arenaframes_shown = {}, actionbars_shown = {}, nameplates_shown = {}, actionbar_buttons = {}, } add_resolved_frame(groups.unitframes_shown, _G.PlayerFrame, "PlayerFrame", report_missing) add_resolved_frame( groups.unitframes_shown, _G.TargetFrame, "TargetFrame", report_missing, true ) add_resolved_frame(groups.unitframes_shown, _G.FocusFrame, "FocusFrame", report_missing) add_resolved_frame(groups.unitframes_shown, _G.PetFrame, "PetFrame", report_missing) add_resolved_frame( groups.unitframes_shown, _G.PlayerCastingBarFrame, "PlayerCastingBarFrame", report_missing ) add_resolved_frame( groups.unitframes_shown, _G.OverlayPlayerCastingBarFrame, "OverlayPlayerCastingBarFrame", report_missing ) add_resolved_frame( groups.unitframes_shown, _G.TargetFrameSpellBar, "TargetFrameSpellBar", report_missing ) add_resolved_frame( groups.unitframes_shown, _G.FocusFrameSpellBar, "FocusFrameSpellBar", report_missing ) add_resolved_frame( groups.unitframes_shown, _G.PetCastingBarFrame, "PetCastingBarFrame", report_missing ) if C_NamePlate and type(C_NamePlate.GetNamePlates) == "function" then local nameplate_bases = C_NamePlate.GetNamePlates() for _, nameplate_base in pairs(nameplate_bases) do if nameplate_base and nameplate_base.UnitFrame then add_resolved_frame( groups.nameplates_shown, nameplate_base.UnitFrame, "NamePlateUnitFrame", false ) end end elseif report_missing and not blizzard_missing_frame_warnings.C_NamePlate then blizzard_missing_frame_warnings.C_NamePlate = true ns:chat_message("Blizzard-Plakettenverwaltung ist nicht verfügbar", true) end add_resolved_frame(groups.groupframes_shown, _G.PartyFrame, "PartyFrame", report_missing) add_resolved_frame( groups.raidframes_shown, _G.CompactRaidFrameContainer, "CompactRaidFrameContainer", report_missing ) add_resolved_frame( groups.raidframes_shown, _G.CompactRaidFrameManager, "CompactRaidFrameManager", report_missing ) local _, instance_type = IsInInstance() local arena_frame_expected = instance_type == "arena" add_resolved_frame( groups.arenaframes_shown, _G.CompactArenaFrame, "CompactArenaFrame", report_missing and arena_frame_expected ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MainActionBar, "MainActionBar", report_missing, false ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MainMenuBar, "MainMenuBar", report_missing, false, main_menu_bar_visibility_events ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBarBottomLeft, "MultiBarBottomLeft", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBarBottomRight, "MultiBarBottomRight", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBarRight, "MultiBarRight", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBarLeft, "MultiBarLeft", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBar5, "MultiBar5", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBar6, "MultiBar6", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MultiBar7, "MultiBar7", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.StanceBar, "StanceBar", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.PetActionBar, "PetActionBar", report_missing, true ) add_resolved_actionbar_buttons(groups.actionbar_buttons, "ActionButton", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBarBottomLeftButton", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBarBottomRightButton", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBarRightButton", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBarLeftButton", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBar5Button", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBar6Button", report_missing) add_resolved_actionbar_buttons(groups.actionbar_buttons, "MultiBar7Button", report_missing) add_resolved_actionbar_frame( groups.actionbars_shown, _G.BagsBar, "BagsBar", report_missing, true ) add_resolved_actionbar_frame( groups.actionbars_shown, _G.MicroMenu, "MicroMenu", report_missing, true ) -- PossessActionBar remains Blizzard-controlled; slots 121-132 are project-excluded. return groups end local function enforce_hidden_blizzard_frame(frame) if not blizzard_hidden_frames[frame] then return end if InCombatLockdown() and type(frame.IsProtected) == "function" and frame:IsProtected() then blizzard_visibility_pending = true return end if frame:IsShown() then frame:Hide() end end local function ensure_blizzard_visibility_hook(frame) if blizzard_hooked_frames[frame] then return end blizzard_hooked_frames[frame] = true hooksecurefunc(frame, "Show", function(target) enforce_hidden_blizzard_frame(target) end) hooksecurefunc(frame, "SetShown", function(target, shown) if shown then enforce_hidden_blizzard_frame(target) end end) end local function get_blizzard_hidden_parent() if blizzard_hidden_parent then return blizzard_hidden_parent end blizzard_hidden_parent = CreateFrame("Frame", nil, UIParent) blizzard_hidden_parent:Hide() return blizzard_hidden_parent end local function detach_hidden_blizzard_frame(frame) if blizzard_event_detached_frames[frame] then return true end if InCombatLockdown() then blizzard_visibility_pending = true return false end if blizzard_event_detach_frames[frame] then frame:UnregisterAllEvents() end local partial_events = blizzard_partial_event_detach_frames[frame] if partial_events then for index = 1, #partial_events do frame:UnregisterEvent(partial_events[index]) end end if frame.HideBase then frame:HideBase() else frame:Hide() end frame:SetParent(get_blizzard_hidden_parent()) blizzard_event_detached_frames[frame] = true return true end local function detach_hidden_blizzard_action_button(button) if blizzard_action_button_detached_frames[button] then return true end if InCombatLockdown() then blizzard_visibility_pending = true return false end button:Hide() button:UnregisterAllEvents() button:SetAttribute("statehidden", true) blizzard_action_button_detached_frames[button] = true return true end local function apply_blizzard_visibility(report_missing) if not blizzard_profile_applied then return false end if InCombatLockdown() then blizzard_visibility_pending = true return false end blizzard_visibility_pending = false local frame_groups = resolve_blizzard_frames(report_missing == true) for index = 1, #blizzard_visibility_order do local key = blizzard_visibility_order[index] local should_show = blizzard_visibility_state[key] == true local frames = frame_groups[key] for frame_index = 1, #frames do local frame = frames[frame_index] if should_show then blizzard_hidden_frames[frame] = nil else if not blizzard_parent_detach_frames[frame] then ensure_blizzard_visibility_hook(frame) end blizzard_hidden_frames[frame] = true end end end for index = 1, #blizzard_visibility_order do local key = blizzard_visibility_order[index] if blizzard_visibility_state[key] ~= true then local frames = frame_groups[key] for frame_index = 1, #frames do local frame = frames[frame_index] if blizzard_parent_detach_frames[frame] then detach_hidden_blizzard_frame(frame) elseif frame:IsShown() then frame:Hide() end end end end if blizzard_visibility_state.actionbars_shown ~= true then local buttons = frame_groups.actionbar_buttons for index = 1, #buttons do detach_hidden_blizzard_action_button(buttons[index]) end end return true end function ns:set_blizzard_nameplates_shown(shown) if type(shown) ~= "boolean" then return false end blizzard_visibility_state.nameplates_shown = shown if not blizzard_profile_applied then return true end return apply_blizzard_visibility(false) end function ns:get_blizzard_nameplates_shown() return blizzard_visibility_state.nameplates_shown == true end function ns:refresh_blizzard_nameplate_visibility() if not blizzard_profile_applied then return false end return apply_blizzard_visibility(false) end local function apply_blizzard_profile_data(data) if type(data) ~= "table" then return false end blizzard_profile_applied = true for index = 1, #blizzard_option_order do local key = blizzard_option_order[index] local definition = blizzard_option_definitions[key] local value = data[key] if type(value) == "boolean" then blizzard_visibility_state[key] = value else blizzard_visibility_state[key] = definition.default end end return apply_blizzard_visibility() end local function handle_blizzard_visibility_event(_, event, loaded_addon_name) if not blizzard_profile_applied then return end if event == "PLAYER_REGEN_ENABLED" then if blizzard_visibility_pending then apply_blizzard_visibility(false) end return end if event == "ADDON_LOADED" then if not blizzard_visibility_addons[loaded_addon_name] then return end apply_blizzard_visibility(false) return end apply_blizzard_visibility(event == "PLAYER_ENTERING_WORLD") end local function collect_actionbar_profile_data() local data = {} if type(ns.get_actionbar_settings) ~= "function" then return data end if type(ns.get_actionbar_general_settings) == "function" then local lock_modifier, cooldown_numbers_enabled, tooltip_enabled = ns:get_actionbar_general_settings() data.allgemein = { lock_modifier = lock_modifier, cooldown_numbers_enabled = cooldown_numbers_enabled == true, tooltip_enabled = tooltip_enabled ~= false, } end for index = 1, #actionbar_option_order do local bar_key = actionbar_option_order[index] local definition = actionbar_option_definitions[bar_key] local button_count, spacing, offset_x, offset_y, swap_target, range_check_enabled, enabled, buttons_per_row, scale = ns:get_actionbar_settings(bar_key) if spacing ~= nil then local settings = { spacing = spacing, scale = scale, offset_x = offset_x, offset_y = offset_y, buttons_per_row = buttons_per_row, } if bar_key ~= "leiste1" then settings.enabled = enabled ~= false end if definition.numbered then settings.button_count = button_count end if definition.numbered then settings.swap_target = swap_target settings.range_check_enabled = range_check_enabled == true end data[bar_key] = settings end end return data end local function apply_actionbar_profile_data(data) if type(data) ~= "table" or type(ns.set_actionbar_spacing) ~= "function" or type(ns.set_actionbar_offset) ~= "function" or type(ns.set_actionbar_scale) ~= "function" then return false end local general_settings = data.allgemein if type(general_settings) == "table" then if type(general_settings.lock_modifier) == "string" and type(ns.set_actionbar_lock_modifier) == "function" then ns:set_actionbar_lock_modifier(general_settings.lock_modifier) end if type(general_settings.cooldown_numbers_enabled) == "boolean" and type(ns.set_actionbar_cooldown_numbers) == "function" then ns:set_actionbar_cooldown_numbers(general_settings.cooldown_numbers_enabled) end if type(general_settings.tooltip_enabled) == "boolean" and type(ns.set_actionbar_tooltip_enabled) == "function" then ns:set_actionbar_tooltip_enabled(general_settings.tooltip_enabled) end end for index = 1, #actionbar_option_order do local bar_key = actionbar_option_order[index] local definition = actionbar_option_definitions[bar_key] local settings = data[bar_key] if type(settings) == "table" then if definition.numbered and type(settings.button_count) == "number" then ns:set_actionbar_button_count(bar_key, settings.button_count) end if type(settings.buttons_per_row) == "number" and type(ns.set_actionbar_buttons_per_row) == "function" then ns:set_actionbar_buttons_per_row(bar_key, settings.buttons_per_row) end if type(settings.spacing) == "number" then ns:set_actionbar_spacing(bar_key, settings.spacing) end if type(settings.scale) == "number" then ns:set_actionbar_scale(bar_key, settings.scale) end if type(settings.offset_x) == "number" and type(settings.offset_y) == "number" then ns:set_actionbar_offset(bar_key, settings.offset_x, settings.offset_y) end if definition.numbered then ns:set_actionbar_swap_target(bar_key, settings.swap_target) if type(settings.range_check_enabled) == "boolean" then ns:set_actionbar_range_check(bar_key, settings.range_check_enabled) end end if bar_key ~= "leiste1" and type(settings.enabled) == "boolean" and type(ns.set_actionbar_enabled) == "function" then ns:set_actionbar_enabled(bar_key, settings.enabled) end end end return true end function module:mark_options_dirty() if type(ns.options_working_copy) ~= "table" then return false end if self.options_dirty then return true end self.options_dirty = true if self.options_save_button then self.options_save_button.frame:Enable() end return true end function module:begin_options_edit() local active_profile = type(ns.get_active_profile) == "function" and ns:get_active_profile() or nil if type(active_profile) ~= "string" or type(ns.db) ~= "table" or type(ns.db.profiles) ~= "table" or type(ns.db.profiles[active_profile]) ~= "table" then return false end ns.options_working_copy = deep_copy(ns.db.profiles[active_profile]) self.options_dirty = false if self.options_save_button then self.options_save_button.frame:Disable() end self:refresh_unitframe_controls() self:refresh_actionbar_controls() self:refresh_minimap_controls() self:refresh_chat_controls() self:refresh_verbesserungen_controls() self:refresh_zusatzleisten_controls() self:refresh_tooltip_controls() self:refresh_lootfenster_controls() self:refresh_nameplate_controls() self:refresh_blizzard_controls() self:refresh_samui_elements_controls() return true end function module:discard_options_working_copy() ns.options_working_copy = nil self.options_dirty = false if self.options_save_button then self.options_save_button.frame:Disable() end end function module:save_options_working_copy() local active_profile = ns:get_active_profile() local working_copy = ns.options_working_copy if type(active_profile) ~= "string" or type(working_copy) ~= "table" or type(ns.db) ~= "table" or type(ns.db.profiles) ~= "table" or type(ns.db.profiles[active_profile]) ~= "table" then if type(active_profile) == "string" then ns:chat_message( "Aktives Profil '" .. active_profile .. "' konnte nicht gespeichert werden", true ) else ns:chat_message("Aktives Profil konnte nicht gespeichert werden", true) end return false end ns.db.profiles[active_profile] = deep_copy(working_copy) self.options_frame:Hide() ReloadUI() return true end local function get_working_unitframe_settings(unit) local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.unitframes) ~= "table" then return nil end return working_copy.unitframes[unit] end local function set_slider_visual_value(slider, value) local clamped_value = clamp_value(value, slider.minimum, slider.maximum) local rounded_value = round_to_step(clamped_value, slider.step) rounded_value = clamp_value(rounded_value, slider.minimum, slider.maximum) slider.current_value = rounded_value slider.value_label:SetText(slider.label .. ": " .. rounded_value) if slider.frame:GetValue() ~= rounded_value then slider.updating_visual = true slider.frame:SetValue(rounded_value) slider.updating_visual = false end end local function apply_unitframe_slider_value(section, slider, value) local settings = get_working_unitframe_settings(section.unit) if type(settings) ~= "table" then return end local clamped_value = clamp_value(value, slider.minimum, slider.maximum) local rounded_value = round_to_step(clamped_value, slider.step) rounded_value = clamp_value(rounded_value, slider.minimum, slider.maximum) set_slider_visual_value(slider, rounded_value) if settings[slider.key] ~= rounded_value then settings[slider.key] = rounded_value module:mark_options_dirty() end end local function create_slider_row( parent, section, row_definition, minimum, maximum, apply_value, palette, designelemente ) local row = CreateFrame("Frame", nil, parent) row:SetSize(actionbar_row_width, actionbar_slider_row_height) local value_label = row:CreateFontString(nil, "OVERLAY") value_label:SetFontObject(samui_font_12) value_label:SetSize(actionbar_control_label_width, actionbar_slider_row_height) value_label:SetPoint("LEFT", row, "LEFT", 0, 0) value_label:SetJustifyH("LEFT") value_label:SetJustifyV("MIDDLE") value_label:SetTextColor(palette:get_rgb("text")) slider_counter = slider_counter + 1 local slider_name = "samui_options_slider_" .. slider_counter local slider_container = CreateFrame( "Frame", slider_name, row, "MinimalSliderWithSteppersTemplate" ) slider_container:SetWidth(actionbar_control_width) slider_container:SetPoint( "LEFT", value_label, "RIGHT", actionbar_control_gap, 0 ) apply_minimal_slider_fonts(slider_container) local slider_frame = slider_container.Slider slider_frame:SetMinMaxValues(minimum, maximum) slider_frame:SetValueStep(row_definition.step) slider_frame:SetObeyStepOnDrag(true) local title_label = slider_container.TopText local low_label = slider_container.MinText local high_label = slider_container.MaxText if title_label then title_label:Hide() end if low_label then low_label:SetText(tostring(minimum)) low_label:Show() end if high_label then high_label:SetText(tostring(maximum)) high_label:Show() end local slider = { key = row_definition.key, label = row_definition.label, minimum = minimum, maximum = maximum, step = row_definition.step, frame = slider_frame, container = slider_container, value_label = value_label, current_value = minimum, apply_value = apply_value, updating_visual = false, } slider_frame:SetScript("OnValueChanged", function(_, value) if slider.updating_visual then return end slider.apply_value(section, slider, value) end) set_slider_visual_value(slider, minimum) return row, slider end local function create_unitframe_slider_row( parent, section, row_definition, unit_definition, palette, designelemente ) local minimum, maximum = get_slider_range(unit_definition, row_definition.key) return create_slider_row( parent, section, row_definition, minimum, maximum, apply_unitframe_slider_value, palette, designelemente ) end local function set_slider_editable(slider, editable, palette) if not slider then return end if slider.container and type(slider.container.SetEnabled) == "function" then slider.container:SetEnabled(editable) elseif slider.frame then slider.frame:SetEnabled(editable) end if slider.value_label then slider.value_label:SetTextColor( palette:get_rgb(editable and "text" or "text_muted") ) end end local function find_dropdown_option_index(dropdown_info, value) if not dropdown_info or type(dropdown_info.options) ~= "table" then return nil end for index = 1, #dropdown_info.options do local option = dropdown_info.options[index] if dropdown_info.resolve_value(option.value) == value then return index end end return nil end local function update_dropdown_stepper_state(dropdown_info) if not dropdown_info or not dropdown_info.control then return end local selected_index = find_dropdown_option_index( dropdown_info, dropdown_info.current_value ) local editable = dropdown_info.editable ~= false if dropdown_info.control.DecrementButton then dropdown_info.control.DecrementButton:SetEnabled( editable and selected_index ~= nil and selected_index > 1 ) end if dropdown_info.control.IncrementButton then dropdown_info.control.IncrementButton:SetEnabled( editable and selected_index ~= nil and selected_index < #dropdown_info.options ) end end local function configure_dropdown_steppers(dropdown_info, get_value, select_value) if not dropdown_info or not dropdown_info.control then return end local function step_value(direction) if dropdown_info.editable == false then return end local selected_index = find_dropdown_option_index(dropdown_info, get_value()) if not selected_index then return end local target_index = selected_index + direction local target_option = dropdown_info.options[target_index] if target_option then select_value(target_option.value) end end if dropdown_info.control.DecrementButton then dropdown_info.control.DecrementButton:SetScript("OnClick", function() step_value(-1) end) end if dropdown_info.control.IncrementButton then dropdown_info.control.IncrementButton:SetScript("OnClick", function() step_value(1) end) end end local function set_dropdown_editable(dropdown_info, editable, palette) if not dropdown_info then return end dropdown_info.editable = editable == true if dropdown_info.control and type(dropdown_info.control.SetEnabled) == "function" then dropdown_info.control:SetEnabled(editable) elseif dropdown_info.dropdown then dropdown_info.dropdown:SetEnabled(editable) end if dropdown_info.label then dropdown_info.label:SetTextColor( palette:get_rgb(editable and "text" or "text_muted") ) end update_dropdown_stepper_state(dropdown_info) end local function set_boolean_dropdown_visual(dropdown_info, value) if not dropdown_info then return end dropdown_info.current_value = value == true if dropdown_info.dropdown and dropdown_info.dropdown.Text then dropdown_info.dropdown.Text:SetText( dropdown_info.current_value and (dropdown_info.true_label or "Aktiviert") or (dropdown_info.false_label or "Deaktiviert") ) end update_dropdown_stepper_state(dropdown_info) end local function create_boolean_dropdown_row( parent, control_name, label_text, get_value, set_value, palette, designelemente, refresh_callback, true_label, false_label ) local row = CreateFrame("Frame", nil, parent) row:SetSize(actionbar_row_width, actionbar_dropdown_row_height) local label = row:CreateFontString(nil, "OVERLAY") label:SetFontObject(samui_font_12) label:SetSize(actionbar_control_label_width, actionbar_dropdown_row_height) label:SetPoint("LEFT", row, "LEFT", 0, 0) label:SetJustifyH("LEFT") label:SetJustifyV("MIDDLE") label:SetText(label_text) label:SetTextColor(palette:get_rgb("text")) local dropdown_control = CreateFrame( "Frame", control_name, row, "SettingsDropdownWithButtonsTemplate" ) dropdown_control:SetPoint( "LEFT", label, "RIGHT", actionbar_control_gap, 0 ) dropdown_control:SetWidth(actionbar_control_width) if dropdown_control.Label then dropdown_control.Label:Hide() apply_samui_font(dropdown_control.Label, samui_font_12) end local dropdown = dropdown_control.Dropdown dropdown:SetWidth(actionbar_dropdown_width) apply_samui_font(dropdown.Text, samui_font_12) if type(dropdown_control.SetSteppersShown) == "function" then dropdown_control:SetSteppersShown(true) end local dropdown_info = { control = dropdown_control, dropdown = dropdown, label = label, options = { { value = true, label = true_label or "Aktiviert" }, { value = false, label = false_label or "Deaktiviert" }, }, resolve_value = function(value) return value end, true_label = true_label or "Aktiviert", false_label = false_label or "Deaktiviert", default_label = true_label or "Aktiviert", editable = true, } dropdown:SetDefaultText(dropdown_info.default_label) local function is_selected(option_value) return get_value() == option_value end local function select_value(option_value) local current_value = get_value() if current_value ~= option_value then set_value(option_value) module:mark_options_dirty() end set_boolean_dropdown_visual(dropdown_info, option_value) if refresh_callback then refresh_callback() end end dropdown:SetupMenu(function(_, root_description) for index = 1, #dropdown_info.options do local option = dropdown_info.options[index] local description = root_description:CreateHighlightRadio( option.label, is_selected, select_value, option.value ) description:AddInitializer(function(button) local font_string = button.Text or button.fontString if not font_string and type(button.GetFontString) == "function" then font_string = button:GetFontString() end apply_samui_font(font_string, samui_font_12) end) end end) configure_dropdown_steppers(dropdown_info, get_value, select_value) set_boolean_dropdown_visual(dropdown_info, get_value()) return row, dropdown_info end local function set_selection_dropdown_visual(dropdown_info, value) if not dropdown_info then return end dropdown_info.current_value = value local selected_label = dropdown_info.default_label or "" for index = 1, #dropdown_info.options do local option = dropdown_info.options[index] if option.value == value then selected_label = option.label break end end if dropdown_info.dropdown and dropdown_info.dropdown.Text then dropdown_info.dropdown.Text:SetText(selected_label) end update_dropdown_stepper_state(dropdown_info) end local function create_selection_dropdown_row( parent, control_name, label_text, options, get_value, set_value, palette ) local row = CreateFrame("Frame", nil, parent) row:SetSize(actionbar_row_width, actionbar_dropdown_row_height) local label = row:CreateFontString(nil, "OVERLAY") label:SetFontObject(samui_font_12) label:SetSize(actionbar_control_label_width, actionbar_dropdown_row_height) label:SetPoint("LEFT", row, "LEFT", 0, 0) label:SetJustifyH("LEFT") label:SetJustifyV("MIDDLE") label:SetText(label_text) label:SetTextColor(palette:get_rgb("text")) local dropdown_control = CreateFrame( "Frame", control_name, row, "SettingsDropdownWithButtonsTemplate" ) dropdown_control:SetPoint( "LEFT", label, "RIGHT", actionbar_control_gap, 0 ) dropdown_control:SetWidth(actionbar_control_width) if dropdown_control.Label then dropdown_control.Label:Hide() apply_samui_font(dropdown_control.Label, samui_font_12) end local dropdown = dropdown_control.Dropdown dropdown:SetWidth(actionbar_dropdown_width) apply_samui_font(dropdown.Text, samui_font_12) if type(dropdown_control.SetSteppersShown) == "function" then dropdown_control:SetSteppersShown(true) end local dropdown_info = { control = dropdown_control, dropdown = dropdown, label = label, options = options, resolve_value = function(value) return value end, default_label = options[1] and options[1].label or "", editable = true, } dropdown:SetDefaultText(dropdown_info.default_label) local function is_selected(option_value) return get_value() == option_value end local function select_value(option_value) if get_value() ~= option_value then set_value(option_value) module:mark_options_dirty() end set_selection_dropdown_visual(dropdown_info, option_value) end dropdown:SetupMenu(function(_, root_description) for index = 1, #options do local option = options[index] local description = root_description:CreateHighlightRadio( option.label, is_selected, select_value, option.value ) description:AddInitializer(function(button) local font_string = button.Text or button.fontString if not font_string and type(button.GetFontString) == "function" then font_string = button:GetFontString() end apply_samui_font(font_string, samui_font_12) end) end end) configure_dropdown_steppers(dropdown_info, get_value, select_value) set_selection_dropdown_visual(dropdown_info, get_value()) return row, dropdown_info end local function create_three_option_dropdown_row( parent, control_name, label_text, options, get_value, set_value, palette ) if type(options) ~= "table" or #options ~= 3 then return nil, nil end return create_selection_dropdown_row( parent, control_name, label_text, options, get_value, set_value, palette ) end local function create_unitframe_enabled_row(parent, section, palette, designelemente) return create_boolean_dropdown_row( parent, "samui_unitframe_" .. section.unit .. "_enabled_dropdown", "Unitframe aktiviert", function() local settings = get_working_unitframe_settings(section.unit) if type(settings) == "table" then return settings.enabled ~= false end return true end, function(value) local settings = get_working_unitframe_settings(section.unit) if type(settings) == "table" then settings.enabled = value == true end end, palette, designelemente, function() module:refresh_unitframe_controls() end ) end local function create_unitframe_aggro_glow_row(parent, section, palette, designelemente) return create_boolean_dropdown_row( parent, "samui_unitframe_" .. section.unit .. "_aggro_glow_dropdown", "Aggro Glow", function() local settings = get_working_unitframe_settings(section.unit) if type(settings) == "table" then return settings.aggro_glow_enabled ~= false end return true end, function(value) local settings = get_working_unitframe_settings(section.unit) if type(settings) == "table" then settings.aggro_glow_enabled = value == true end end, palette, designelemente ) end local function create_unitframe_health_smoothing_row(parent, section, palette, designelemente) return create_boolean_dropdown_row( parent, "samui_unitframe_" .. section.unit .. "_health_smoothing_dropdown", "Smooth Health", function() local settings = get_working_unitframe_settings(section.unit) if type(settings) == "table" then return settings.health_smoothing_enabled ~= false end return true end, function(value) local settings = get_working_unitframe_settings(section.unit) if type(settings) == "table" then settings.health_smoothing_enabled = value == true end end, palette, designelemente ) end function module:refresh_unitframe_controls() if not self.unitframe_option_sections or type(ns.options_working_copy) ~= "table" then return false end for index = 1, #unitframe_option_order do local unit = unitframe_option_order[index] local section = self.unitframe_option_sections[unit] local settings = get_working_unitframe_settings(unit) if section and type(settings) == "table" then if section.enabled_dropdown then set_boolean_dropdown_visual(section.enabled_dropdown, settings.enabled ~= false) end if section.aggro_glow_dropdown then set_boolean_dropdown_visual( section.aggro_glow_dropdown, settings.aggro_glow_enabled ~= false ) end if section.health_smoothing_dropdown then set_boolean_dropdown_visual( section.health_smoothing_dropdown, settings.health_smoothing_enabled ~= false ) end set_slider_visual_value(section.sliders.width, settings.width) set_slider_visual_value(section.sliders.height, settings.height) set_slider_visual_value(section.sliders.offset_x, settings.offset_x) set_slider_visual_value(section.sliders.offset_y, settings.offset_y) local controls_editable = unit == "player" or settings.enabled ~= false set_slider_editable(section.sliders.width, controls_editable, ns.palette) set_slider_editable(section.sliders.height, controls_editable, ns.palette) set_slider_editable(section.sliders.offset_x, controls_editable, ns.palette) set_slider_editable(section.sliders.offset_y, controls_editable, ns.palette) end end return true end function module:build_unitframe_options() if self.unitframe_option_sections then return self:refresh_unitframe_controls() end if not self.unitframe_content_frames then return false end local palette = ns.palette local designelemente = ns.designelemente local sections = {} for section_index = 1, #unitframe_option_order do local unit = unitframe_option_order[section_index] local unit_definition = unitframe_option_definitions[unit] local content_frame = self.unitframe_content_frames[unit] if content_frame then local page_heading, page_divider = create_settings_page_header( content_frame, unit_definition.label, palette, designelemente ) local section = { unit = unit, sliders = {}, } sections[unit] = section local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText( "Allgemeine Einstellungen zum " .. unit_definition.label .. " Unitframe" ) general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_general_row if unit ~= "player" then local enabled_row, enabled_dropdown = create_unitframe_enabled_row( content_frame, section, palette, designelemente ) enabled_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) section.enabled_dropdown = enabled_dropdown previous_general_row = enabled_row end local aggro_glow_row, aggro_glow_dropdown = create_unitframe_aggro_glow_row( content_frame, section, palette, designelemente ) if previous_general_row then aggro_glow_row:SetPoint( "TOPLEFT", previous_general_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else aggro_glow_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end section.aggro_glow_dropdown = aggro_glow_dropdown local health_smoothing_row, health_smoothing_dropdown = create_unitframe_health_smoothing_row( content_frame, section, palette, designelemente ) health_smoothing_row:SetPoint( "TOPLEFT", aggro_glow_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) section.health_smoothing_dropdown = health_smoothing_dropdown local position_heading = content_frame:CreateFontString(nil, "OVERLAY") position_heading:SetFontObject(samui_font_12) position_heading:SetPoint( "TOPLEFT", health_smoothing_row, "BOTTOMLEFT", 0, -20 ) position_heading:SetText( "Positions- und Größeneinstellungen zum " .. unit_definition.label .. " Unitframe" ) position_heading:SetTextColor(palette:get_rgb("text")) local position_divider = content_frame:CreateTexture(nil, "ARTWORK") position_divider:SetAtlas("Options_HorizontalDivider", true) position_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) position_divider:SetPoint( "TOPLEFT", position_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row for row_index = 1, #unitframe_row_definitions do local row_definition = unitframe_row_definitions[row_index] local row, slider = create_unitframe_slider_row( content_frame, section, row_definition, unit_definition, palette, designelemente ) if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else row:SetPoint( "TOPLEFT", position_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row section.sliders[row_definition.key] = slider end end end self.unitframe_option_sections = sections return self:refresh_unitframe_controls() end local function get_working_actionbar_settings(bar_key) local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.aktionsleisten) ~= "table" then return nil end return working_copy.aktionsleisten[bar_key] end local function set_actionbar_dropdown_visual(dropdown_info, value) if not dropdown_info then return end dropdown_info.current_value = value local resolved_label = dropdown_info.default_label or "" for index = 1, #dropdown_info.options do local option = dropdown_info.options[index] local resolved_value = dropdown_info.resolve_value(option.value) if resolved_value == value then resolved_label = option.label break end end if dropdown_info.dropdown and dropdown_info.dropdown.Text then dropdown_info.dropdown.Text:SetText(resolved_label) end update_dropdown_stepper_state(dropdown_info) end local function format_actionbar_slider_value(slider, value) return tostring(value) .. (slider.suffix or "") end local function set_actionbar_slider_visual(slider, value) local clamped_value = clamp_value(value, slider.minimum, slider.maximum) local rounded_value = round_to_step(clamped_value, slider.step) rounded_value = clamp_value(rounded_value, slider.minimum, slider.maximum) slider.current_value = rounded_value if slider.value_label then slider.value_label:SetText( slider.label .. ": " .. format_actionbar_slider_value(slider, rounded_value) ) end if slider.frame:GetValue() ~= rounded_value then slider.updating_visual = true slider.frame:SetValue(rounded_value) slider.updating_visual = false end end local function apply_actionbar_native_slider_value(section, slider, value) local settings = get_working_actionbar_settings(section.bar_key) if type(settings) ~= "table" then return end local clamped_value = clamp_value(value, slider.minimum, slider.maximum) local rounded_value = round_to_step(clamped_value, slider.step) rounded_value = clamp_value(rounded_value, slider.minimum, slider.maximum) set_actionbar_slider_visual(slider, rounded_value) if settings[slider.key] ~= rounded_value then settings[slider.key] = rounded_value module:mark_options_dirty() end end local function create_actionbar_native_slider_row(parent, section, row_definition, palette, designelemente) local row = CreateFrame("Frame", nil, parent) row:SetSize(actionbar_row_width, actionbar_slider_row_height) local value_label = row:CreateFontString(nil, "OVERLAY") value_label:SetFontObject(samui_font_12) value_label:SetSize(actionbar_control_label_width, actionbar_slider_row_height) value_label:SetPoint("LEFT", row, "LEFT", 0, 0) value_label:SetJustifyH("LEFT") value_label:SetJustifyV("MIDDLE") value_label:SetTextColor(palette:get_rgb("text")) local slider_name = "samui_actionbar_" .. section.bar_key .. "_" .. row_definition.key .. "_slider" local slider_container = CreateFrame( "Frame", slider_name, row, "MinimalSliderWithSteppersTemplate" ) slider_container:SetWidth(actionbar_control_width) slider_container:SetPoint( "LEFT", value_label, "RIGHT", actionbar_control_gap, 0 ) apply_minimal_slider_fonts(slider_container) local slider_frame = slider_container.Slider slider_frame:SetMinMaxValues(row_definition.minimum, row_definition.maximum) slider_frame:SetValueStep(row_definition.step) slider_frame:SetObeyStepOnDrag(true) local title_label = slider_container.TopText local low_label = slider_container.MinText local high_label = slider_container.MaxText if title_label then title_label:Hide() end if low_label then low_label:SetText(tostring(row_definition.minimum) .. (row_definition.suffix or "")) low_label:Show() end if high_label then high_label:SetText(tostring(row_definition.maximum) .. (row_definition.suffix or "")) high_label:Show() end local slider = { key = row_definition.key, label = row_definition.label, minimum = row_definition.minimum, maximum = row_definition.maximum, step = row_definition.step, frame = slider_frame, container = slider_container, value_label = value_label, current_value = row_definition.minimum, updating_visual = false, section = section, } slider_frame:SetScript("OnValueChanged", function(_, value) if slider.updating_visual then return end apply_actionbar_native_slider_value(section, slider, value) end) set_actionbar_slider_visual(slider, row_definition.minimum) return row, slider end local function create_actionbar_dropdown_row( parent, section, key, label_text, options, get_value, set_value, palette, designelemente, resolve_value ) local row = CreateFrame("Frame", nil, parent) row:SetSize(actionbar_row_width, actionbar_dropdown_row_height) local label = row:CreateFontString(nil, "OVERLAY") label:SetFontObject(samui_font_12) label:SetSize(actionbar_control_label_width, actionbar_dropdown_row_height) label:SetPoint("LEFT", row, "LEFT", 0, 0) label:SetJustifyH("LEFT") label:SetJustifyV("MIDDLE") label:SetText(label_text) label:SetTextColor(palette:get_rgb("text")) local dropdown_name = "samui_actionbar_" .. section.bar_key .. "_" .. key .. "_dropdown" local dropdown_control = CreateFrame( "Frame", dropdown_name, row, "SettingsDropdownWithButtonsTemplate" ) dropdown_control:SetPoint( "LEFT", label, "RIGHT", actionbar_control_gap, 0 ) dropdown_control:SetWidth(actionbar_control_width) if dropdown_control.Label then dropdown_control.Label:Hide() apply_samui_font(dropdown_control.Label, samui_font_12) end local dropdown = dropdown_control.Dropdown dropdown:SetWidth(actionbar_dropdown_width) apply_samui_font(dropdown.Text, samui_font_12) if type(dropdown_control.SetSteppersShown) == "function" then dropdown_control:SetSteppersShown(true) end local resolver = resolve_value or function(value) return value end local dropdown_info = { control = dropdown_control, dropdown = dropdown, label = label, options = options, resolve_value = resolver, default_label = options[1] and options[1].label or "", editable = true, } dropdown:SetDefaultText(dropdown_info.default_label) local function is_selected(option_value) return get_value() == resolver(option_value) end local function select_value(option_value) local resolved_value = resolver(option_value) local current_value = get_value() if current_value ~= resolved_value then set_value(resolved_value) module:mark_options_dirty() end set_actionbar_dropdown_visual(dropdown_info, resolved_value) end dropdown:SetupMenu(function(_, root_description) for index = 1, #options do local option = options[index] local description = root_description:CreateHighlightRadio( option.label, is_selected, select_value, option.value ) description:AddInitializer(function(button) local font_string = button.Text or button.fontString if not font_string and type(button.GetFontString) == "function" then font_string = button:GetFontString() end apply_samui_font(font_string, samui_font_12) end) end end) configure_dropdown_steppers(dropdown_info, get_value, select_value) set_actionbar_dropdown_visual(dropdown_info, get_value()) return row, dropdown_info end local function create_actionbar_lock_modifier_row(parent, section, palette, designelemente) local row, dropdown_info = create_actionbar_dropdown_row( parent, section, "lock_modifier", "Leisten fixieren", actionbar_general_lock_options, function() local settings = get_working_actionbar_settings("allgemein") return type(settings) == "table" and settings.lock_modifier or "SHIFT" end, function(value) local settings = get_working_actionbar_settings("allgemein") if type(settings) == "table" then settings.lock_modifier = value end end, palette, designelemente ) section.lock_modifier_dropdown = dropdown_info return row end local function create_actionbar_tooltip_row(parent, section, palette, designelemente) local options = { { value = true, label = "Aktiviert" }, { value = false, label = "Deaktiviert" }, } local row, dropdown_info = create_actionbar_dropdown_row( parent, section, "tooltip_enabled", "Tooltip anzeigen", options, function() local settings = get_working_actionbar_settings("allgemein") return type(settings) == "table" and settings.tooltip_enabled ~= false end, function(value) local settings = get_working_actionbar_settings("allgemein") if type(settings) == "table" then settings.tooltip_enabled = value == true end end, palette, designelemente ) section.tooltip_dropdown = dropdown_info return row end local function create_actionbar_cooldown_numbers_row(parent, section, palette, designelemente) local row, dropdown_info = create_actionbar_dropdown_row( parent, section, "cooldown_numbers", "Zahlen für Abklingzeiten", actionbar_general_cooldown_options, function() local settings = get_working_actionbar_settings("allgemein") return type(settings) == "table" and settings.cooldown_numbers_enabled == true or false end, function(value) local settings = get_working_actionbar_settings("allgemein") if type(settings) == "table" then settings.cooldown_numbers_enabled = value == true end end, palette, designelemente ) section.cooldown_numbers_dropdown = dropdown_info return row end local function create_actionbar_enabled_row(parent, section, palette, designelemente) return create_boolean_dropdown_row( parent, "samui_actionbar_" .. section.bar_key .. "_enabled_dropdown", "Leiste aktiviert", function() local settings = get_working_actionbar_settings(section.bar_key) if type(settings) == "table" then return settings.enabled ~= false end return true end, function(value) local settings = get_working_actionbar_settings(section.bar_key) if type(settings) == "table" then settings.enabled = value == true end end, palette, designelemente, function() module:refresh_actionbar_controls() end ) end local function create_actionbar_swap_row(parent, section, palette, designelemente) local options = { { value = false, label = "Deaktiviert" }, } for index = 1, #actionbar_option_order do local target_bar_key = actionbar_option_order[index] local target_definition = actionbar_option_definitions[target_bar_key] if target_definition.numbered and target_bar_key ~= section.bar_key then options[#options + 1] = { value = target_bar_key, label = target_definition.label, } end end local row, dropdown_info = create_actionbar_dropdown_row( parent, section, "swap", "Leistenwechsel", options, function() local settings = get_working_actionbar_settings(section.bar_key) return type(settings) == "table" and settings.swap_target or nil end, function(value) local settings = get_working_actionbar_settings(section.bar_key) if type(settings) == "table" then settings.swap_target = value end end, palette, designelemente, function(value) if value == false then return nil end return value end ) section.swap_dropdown = dropdown_info return row end local function create_actionbar_range_row(parent, section, palette, designelemente) local options = { { value = true, label = "Aktiviert" }, { value = false, label = "Deaktiviert" }, } local row, dropdown_info = create_actionbar_dropdown_row( parent, section, "range_check", "Abstandscheck", options, function() local settings = get_working_actionbar_settings(section.bar_key) return type(settings) == "table" and settings.range_check_enabled == true or false end, function(value) local settings = get_working_actionbar_settings(section.bar_key) if type(settings) == "table" then settings.range_check_enabled = value == true end end, palette, designelemente ) section.range_check_dropdown = dropdown_info return row end function module:refresh_actionbar_controls() if not self.actionbar_option_sections or type(ns.options_working_copy) ~= "table" then return false end local general_section = self.actionbar_option_sections.allgemein local general_values = get_working_actionbar_settings("allgemein") if general_section and type(general_values) == "table" then set_actionbar_dropdown_visual( general_section.lock_modifier_dropdown, general_values.lock_modifier ) set_actionbar_dropdown_visual( general_section.tooltip_dropdown, general_values.tooltip_enabled ~= false ) set_actionbar_dropdown_visual( general_section.cooldown_numbers_dropdown, general_values.cooldown_numbers_enabled == true ) end for index = 1, #actionbar_option_order do local bar_key = actionbar_option_order[index] local section = self.actionbar_option_sections[bar_key] local definition = actionbar_option_definitions[bar_key] local values = get_working_actionbar_settings(bar_key) if section and type(values) == "table" then if section.enabled_dropdown then set_boolean_dropdown_visual(section.enabled_dropdown, values.enabled ~= false) end if definition.numbered then set_actionbar_slider_visual(section.sliders.button_count, values.button_count) end set_actionbar_slider_visual(section.sliders.buttons_per_row, values.buttons_per_row) set_actionbar_slider_visual(section.sliders.spacing, values.spacing) set_actionbar_slider_visual(section.sliders.scale, values.scale) set_actionbar_slider_visual(section.sliders.offset_x, values.offset_x) set_actionbar_slider_visual(section.sliders.offset_y, values.offset_y) if definition.numbered then set_actionbar_dropdown_visual(section.swap_dropdown, values.swap_target) set_actionbar_dropdown_visual( section.range_check_dropdown, values.range_check_enabled == true ) end local controls_editable = bar_key == "leiste1" or values.enabled ~= false if definition.numbered then set_dropdown_editable(section.swap_dropdown, controls_editable, ns.palette) set_dropdown_editable(section.range_check_dropdown, controls_editable, ns.palette) end if definition.numbered then set_slider_editable(section.sliders.button_count, controls_editable, ns.palette) end set_slider_editable(section.sliders.buttons_per_row, controls_editable, ns.palette) set_slider_editable(section.sliders.spacing, controls_editable, ns.palette) set_slider_editable(section.sliders.scale, controls_editable, ns.palette) set_slider_editable(section.sliders.offset_x, controls_editable, ns.palette) set_slider_editable(section.sliders.offset_y, controls_editable, ns.palette) end end return true end function module:build_actionbar_options() if self.actionbar_option_sections then return self:refresh_actionbar_controls() end if not self.actionbar_content_frames then return false end local palette = ns.palette local designelemente = ns.designelemente local sections = {} local general_content_frame = self.actionbar_content_frames.allgemein if general_content_frame then local page_heading, page_divider = create_settings_page_header( general_content_frame, "Allgemein", palette, designelemente ) local general_section = { bar_key = "allgemein", sliders = {}, } sections.allgemein = general_section local general_heading = general_content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zu den Aktionsleisten") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (general_content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local lock_row = create_actionbar_lock_modifier_row( general_content_frame, general_section, palette, designelemente ) lock_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) local tooltip_row = create_actionbar_tooltip_row( general_content_frame, general_section, palette, designelemente ) tooltip_row:SetPoint("TOPLEFT", lock_row, "BOTTOMLEFT", 0, -4) local display_heading = general_content_frame:CreateFontString(nil, "OVERLAY") display_heading:SetFontObject(samui_font_12) display_heading:SetPoint( "TOPLEFT", tooltip_row, "BOTTOMLEFT", 0, -20 ) display_heading:SetText("Darstellungseinstellungen zu den Aktionsleisten") display_heading:SetTextColor(palette:get_rgb("text")) local display_divider = general_content_frame:CreateTexture(nil, "ARTWORK") display_divider:SetAtlas("Options_HorizontalDivider", true) display_divider:SetWidth(math.max(general_content_frame:GetWidth() * 0.90, 1)) display_divider:SetPoint( "TOPLEFT", display_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local cooldown_row = create_actionbar_cooldown_numbers_row( general_content_frame, general_section, palette, designelemente ) cooldown_row:SetPoint( "TOPLEFT", display_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end for section_index = 1, #actionbar_option_order do local bar_key = actionbar_option_order[section_index] local definition = actionbar_option_definitions[bar_key] local content_frame = self.actionbar_content_frames[bar_key] if content_frame then local page_heading, page_divider = create_settings_page_header( content_frame, definition.label, palette, designelemente ) local section = { bar_key = bar_key, sliders = {}, } sections[bar_key] = section local description_target if definition.numbered then description_target = "zur " .. definition.label elseif bar_key == "pet" then description_target = "zur Begleiterleiste" else description_target = "zur Haltungsleiste" end local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen " .. description_target) general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_general_row if bar_key ~= "leiste1" then local enabled_row, enabled_dropdown = create_actionbar_enabled_row( content_frame, section, palette, designelemente ) enabled_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) section.enabled_dropdown = enabled_dropdown previous_general_row = enabled_row end if definition.numbered then local swap_row = create_actionbar_swap_row( content_frame, section, palette, designelemente ) if previous_general_row then swap_row:SetPoint( "TOPLEFT", previous_general_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else swap_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_general_row = swap_row local range_row = create_actionbar_range_row( content_frame, section, palette, designelemente ) range_row:SetPoint( "TOPLEFT", previous_general_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) previous_general_row = range_row end local layout_heading = content_frame:CreateFontString(nil, "OVERLAY") layout_heading:SetFontObject(samui_font_12) layout_heading:SetPoint( "TOPLEFT", previous_general_row, "BOTTOMLEFT", 0, -20 ) layout_heading:SetText( "Positions- und Größeneinstellungen " .. description_target ) layout_heading:SetTextColor(palette:get_rgb("text")) local layout_divider = content_frame:CreateTexture(nil, "ARTWORK") layout_divider:SetAtlas("Options_HorizontalDivider", true) layout_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) layout_divider:SetPoint( "TOPLEFT", layout_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local slider_definitions = {} if definition.numbered then slider_definitions[#slider_definitions + 1] = actionbar_count_slider_definition end slider_definitions[#slider_definitions + 1] = actionbar_buttons_per_row_slider_definition for common_index = 1, #actionbar_common_slider_definitions do slider_definitions[#slider_definitions + 1] = actionbar_common_slider_definitions[common_index] end local previous_slider_row for row_index = 1, #slider_definitions do local row_definition = slider_definitions[row_index] local row, slider = create_actionbar_native_slider_row( content_frame, section, row_definition, palette, designelemente ) if previous_slider_row then row:SetPoint( "TOPLEFT", previous_slider_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else row:SetPoint( "TOPLEFT", layout_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_slider_row = row section.sliders[row_definition.key] = slider end end end self.actionbar_option_sections = sections return self:refresh_actionbar_controls() end local function get_working_minimap_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.minimap) ~= "table" then return nil end return working_copy.minimap end function module:refresh_minimap_controls() if not self.minimap_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_minimap_settings() if type(settings) ~= "table" then return false end for index = 1, #minimap_option_order do local key = minimap_option_order[index] local definition = minimap_option_definitions[key] local value = settings[key] if type(value) ~= "boolean" then value = definition.default settings[key] = value end set_boolean_dropdown_visual(self.minimap_option_rows[key], value) end return true end function module:build_minimap_options() if self.minimap_option_rows then return self:refresh_minimap_controls() end local content_frame = self.content_frames and self.content_frames.minimap if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Minimap", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zur Minimap") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row local display_divider for index = 1, #minimap_option_order do if index == 4 then local display_heading = content_frame:CreateFontString(nil, "OVERLAY") display_heading:SetFontObject(samui_font_12) display_heading:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -20 ) display_heading:SetText("Anzeigeeinstellungen zur Minimap") display_heading:SetTextColor(palette:get_rgb("text")) display_divider = content_frame:CreateTexture(nil, "ARTWORK") display_divider:SetAtlas("Options_HorizontalDivider", true) display_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) display_divider:SetPoint( "TOPLEFT", display_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) previous_row = nil end local key = minimap_option_order[index] local definition = minimap_option_definitions[key] local row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_minimap_" .. key .. "_dropdown", definition.label, function() local settings = get_working_minimap_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_minimap_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) elseif display_divider then row:SetPoint( "TOPLEFT", display_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) else row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row rows[key] = dropdown_info end self.minimap_option_rows = rows return self:refresh_minimap_controls() end local function get_working_chat_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.chat) ~= "table" then return nil end return working_copy.chat end function module:refresh_chat_controls() if not self.chat_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_chat_settings() if type(settings) ~= "table" then return false end for index = 1, #chat_option_order do local key = chat_option_order[index] local definition = chat_option_definitions[key] local value = settings[key] if type(value) ~= "boolean" then value = definition.default settings[key] = value end set_boolean_dropdown_visual(self.chat_option_rows[key], value) end return true end function module:build_chat_options() if self.chat_option_rows then return self:refresh_chat_controls() end local content_frame = self.content_frames and self.content_frames.chat if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Chat", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zum Chat") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row for index = 1, #chat_option_order do local key = chat_option_order[index] local definition = chat_option_definitions[key] local row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_chat_" .. key .. "_dropdown", definition.label, function() local settings = get_working_chat_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_chat_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row rows[key] = dropdown_info end self.chat_option_rows = rows return self:refresh_chat_controls() end local function get_working_verbesserungen_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.verbesserungen) ~= "table" then return nil end return working_copy.verbesserungen end local function normalize_working_auto_repair_mode(value) for index = 1, #auto_repair_options do if auto_repair_options[index].value == value then return value end end return "player" end function module:refresh_verbesserungen_controls() if not self.verbesserungen_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_verbesserungen_settings() if type(settings) ~= "table" then return false end local fast_loot_enabled = settings.fast_loot_enabled if type(fast_loot_enabled) ~= "boolean" then fast_loot_enabled = verbesserungen_boolean_definitions.fast_loot_enabled.default settings.fast_loot_enabled = fast_loot_enabled end set_boolean_dropdown_visual( self.verbesserungen_option_rows.fast_loot_enabled, fast_loot_enabled ) local auto_repair_mode = normalize_working_auto_repair_mode(settings.auto_repair_mode) settings.auto_repair_mode = auto_repair_mode set_selection_dropdown_visual( self.verbesserungen_option_rows.auto_repair_mode, auto_repair_mode ) local auto_sell_enabled = settings.auto_sell_enabled if type(auto_sell_enabled) ~= "boolean" then auto_sell_enabled = verbesserungen_boolean_definitions.auto_sell_enabled.default settings.auto_sell_enabled = auto_sell_enabled end set_boolean_dropdown_visual( self.verbesserungen_option_rows.auto_sell_enabled, auto_sell_enabled ) return true end function module:build_verbesserungen_options() if self.verbesserungen_option_rows then return self:refresh_verbesserungen_controls() end local content_frame = self.content_frames and self.content_frames.verbesserungen if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Verbesserungen", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zu den Verbesserungen") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row for index = 1, #verbesserungen_option_order do local key = verbesserungen_option_order[index] local row local dropdown_info if key == "auto_repair_mode" then row, dropdown_info = create_three_option_dropdown_row( content_frame, "samui_verbesserungen_auto_repair_mode_dropdown", "Automatisch reparieren", auto_repair_options, function() local settings = get_working_verbesserungen_settings() if type(settings) == "table" then return normalize_working_auto_repair_mode(settings.auto_repair_mode) end return "player" end, function(value) local settings = get_working_verbesserungen_settings() if type(settings) == "table" then settings.auto_repair_mode = normalize_working_auto_repair_mode(value) end end, palette ) else local definition = verbesserungen_boolean_definitions[key] row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_verbesserungen_" .. key .. "_dropdown", definition.label, function() local settings = get_working_verbesserungen_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_verbesserungen_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) end if not row or not dropdown_info then return false end if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row rows[key] = dropdown_info end self.verbesserungen_option_rows = rows return self:refresh_verbesserungen_controls() end local function get_working_zusatzleisten_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.zusatzleisten) ~= "table" then return nil end return working_copy.zusatzleisten end function module:refresh_zusatzleisten_controls() if not self.zusatzleisten_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_zusatzleisten_settings() if type(settings) ~= "table" then return false end for index = 1, #zusatzleisten_option_order do local key = zusatzleisten_option_order[index] local definition = zusatzleisten_option_definitions[key] local value = settings[key] if type(value) ~= "boolean" then value = definition.default settings[key] = value end set_boolean_dropdown_visual(self.zusatzleisten_option_rows[key], value) end local verbesserungen_settings = get_working_verbesserungen_settings() if type(verbesserungen_settings) ~= "table" then return false end local totem_bar_enabled = verbesserungen_settings[totem_bar_option_definition.key] if type(totem_bar_enabled) ~= "boolean" then totem_bar_enabled = totem_bar_option_definition.default verbesserungen_settings[totem_bar_option_definition.key] = totem_bar_enabled end set_boolean_dropdown_visual( self.zusatzleisten_option_rows[totem_bar_option_definition.key], totem_bar_enabled ) return true end function module:build_zusatzleisten_options() if self.zusatzleisten_option_rows then return self:refresh_zusatzleisten_controls() end local content_frame = self.content_frames and self.content_frames.zusatzleisten if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Zusatzleisten", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zu den Zusatzleisten") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row for index = 1, #zusatzleisten_option_order do local key = zusatzleisten_option_order[index] local definition = zusatzleisten_option_definitions[key] local row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_zusatzleisten_" .. key .. "_dropdown", definition.label, function() local settings = get_working_zusatzleisten_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_zusatzleisten_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row rows[key] = dropdown_info end local totem_heading = content_frame:CreateFontString(nil, "OVERLAY") totem_heading:SetFontObject(samui_font_12) totem_heading:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -20 ) totem_heading:SetText("Totems") totem_heading:SetTextColor(palette:get_rgb("text")) local totem_divider = content_frame:CreateTexture(nil, "ARTWORK") totem_divider:SetAtlas("Options_HorizontalDivider", true) totem_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) totem_divider:SetPoint( "TOPLEFT", totem_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local totem_row, totem_dropdown_info = create_boolean_dropdown_row( content_frame, "samui_zusatzleisten_totem_bar_enabled_dropdown", totem_bar_option_definition.label, function() local settings = get_working_verbesserungen_settings() if type(settings) == "table" then local value = settings[totem_bar_option_definition.key] if type(value) == "boolean" then return value end end return totem_bar_option_definition.default end, function(value) local settings = get_working_verbesserungen_settings() if type(settings) == "table" then settings[totem_bar_option_definition.key] = value == true end end, palette, designelemente ) if not totem_row or not totem_dropdown_info then return false end totem_row:SetPoint( "TOPLEFT", totem_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) rows[totem_bar_option_definition.key] = totem_dropdown_info self.zusatzleisten_option_rows = rows return self:refresh_zusatzleisten_controls() end local function get_working_tooltip_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.tooltip) ~= "table" then return nil end return working_copy.tooltip end function module:refresh_tooltip_controls() if not self.tooltip_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_tooltip_settings() if type(settings) ~= "table" then return false end for index = 1, #tooltip_option_order do local key = tooltip_option_order[index] local definition = tooltip_option_definitions[key] local value = settings[key] if type(value) ~= "boolean" then value = definition.default settings[key] = value end set_boolean_dropdown_visual(self.tooltip_option_rows[key], value) end return true end function module:build_tooltip_options() if self.tooltip_option_rows then return self:refresh_tooltip_controls() end local content_frame = self.content_frames and self.content_frames.tooltip if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Tooltip", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zum Tooltip") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row local display_divider for index = 1, #tooltip_option_order do if index == 2 then local display_heading = content_frame:CreateFontString(nil, "OVERLAY") display_heading:SetFontObject(samui_font_12) display_heading:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -20 ) display_heading:SetText("Weitere Darstellungseinstellungen zum Tooltip") display_heading:SetTextColor(palette:get_rgb("text")) display_divider = content_frame:CreateTexture(nil, "ARTWORK") display_divider:SetAtlas("Options_HorizontalDivider", true) display_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) display_divider:SetPoint( "TOPLEFT", display_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) previous_row = nil end local key = tooltip_option_order[index] local definition = tooltip_option_definitions[key] local row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_tooltip_" .. key .. "_dropdown", definition.label, function() local settings = get_working_tooltip_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_tooltip_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) elseif display_divider then row:SetPoint( "TOPLEFT", display_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) else row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row rows[key] = dropdown_info end self.tooltip_option_rows = rows return self:refresh_tooltip_controls() end local function get_working_lootfenster_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.lootfenster) ~= "table" then return nil end return working_copy.lootfenster end local function apply_lootfenster_slider_value(_, slider, value) local settings = get_working_lootfenster_settings() if type(settings) ~= "table" then return end local clamped_value = clamp_value(value, slider.minimum, slider.maximum) local rounded_value = round_to_step(clamped_value, slider.step) rounded_value = clamp_value(rounded_value, slider.minimum, slider.maximum) set_slider_visual_value(slider, rounded_value) if settings[slider.key] ~= rounded_value then settings[slider.key] = rounded_value module:mark_options_dirty() end end function module:refresh_lootfenster_controls() if not self.lootfenster_option_slider or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_lootfenster_settings() if type(settings) ~= "table" then return false end local value = settings[lootfenster_slider_definition.key] if type(value) ~= "number" then value = lootfenster_slider_definition.default settings[lootfenster_slider_definition.key] = value end set_slider_visual_value(self.lootfenster_option_slider, value) if self.loot_summary_option_rows then for key, definition in pairs(loot_summary_boolean_definitions) do local boolean_value = settings[key] if type(boolean_value) ~= "boolean" then boolean_value = definition.default settings[key] = boolean_value end set_boolean_dropdown_visual(self.loot_summary_option_rows[key], boolean_value) end local threshold = settings.summary_quality_threshold if type(threshold) ~= "number" or threshold < 0 or threshold > 3 then threshold = 1 settings.summary_quality_threshold = threshold end set_selection_dropdown_visual( self.loot_summary_option_rows.summary_quality_threshold, threshold ) local duration = settings.summary_duration if duration ~= 2 and duration ~= 4 and duration ~= 6 and duration ~= 8 then duration = 4 settings.summary_duration = duration end set_selection_dropdown_visual(self.loot_summary_option_rows.summary_duration, duration) end return true end function module:build_lootfenster_options() if self.lootfenster_option_slider and self.loot_summary_option_rows then return self:refresh_lootfenster_controls() end local content_frame = self.content_frames and self.content_frames.lootfenster if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local page_heading, page_divider = create_settings_page_header( content_frame, "Loot und Beute", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zum Lootfenster") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local section = {} local row, slider = create_slider_row( content_frame, section, lootfenster_slider_definition, lootfenster_slider_definition.minimum, lootfenster_slider_definition.maximum, apply_lootfenster_slider_value, palette, designelemente ) row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) local summary_heading = content_frame:CreateFontString(nil, "OVERLAY") summary_heading:SetFontObject(samui_font_12) summary_heading:SetPoint("TOPLEFT", row, "BOTTOMLEFT", 0, -20) summary_heading:SetText("Loot-Summary") summary_heading:SetTextColor(palette:get_rgb("text")) local summary_divider = content_frame:CreateTexture(nil, "ARTWORK") summary_divider:SetAtlas("Options_HorizontalDivider", true) summary_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) summary_divider:SetPoint( "TOPLEFT", summary_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local summary_rows = {} local previous_row local boolean_order = { "summary_enabled", "summary_gold_enabled", } for index = 1, #boolean_order do local key = boolean_order[index] local definition = loot_summary_boolean_definitions[key] local summary_row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_lootfenster_" .. key .. "_dropdown", definition.label, function() local settings = get_working_lootfenster_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_lootfenster_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) if previous_row then summary_row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) else summary_row:SetPoint( "TOPLEFT", summary_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end summary_rows[key] = dropdown_info previous_row = summary_row end local threshold_row, threshold_dropdown = create_selection_dropdown_row( content_frame, "samui_lootfenster_summary_quality_threshold_dropdown", "Anzeigeschwellwert", loot_summary_quality_options, function() local settings = get_working_lootfenster_settings() if type(settings) == "table" and type(settings.summary_quality_threshold) == "number" then return settings.summary_quality_threshold end return 1 end, function(value) local settings = get_working_lootfenster_settings() if type(settings) == "table" then settings.summary_quality_threshold = value end end, palette ) threshold_row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) summary_rows.summary_quality_threshold = threshold_dropdown previous_row = threshold_row local duration_row, duration_dropdown = create_selection_dropdown_row( content_frame, "samui_lootfenster_summary_duration_dropdown", "Anzeigedauer", loot_summary_duration_options, function() local settings = get_working_lootfenster_settings() if type(settings) == "table" and type(settings.summary_duration) == "number" then return settings.summary_duration end return 4 end, function(value) local settings = get_working_lootfenster_settings() if type(settings) == "table" then settings.summary_duration = value end end, palette ) duration_row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) summary_rows.summary_duration = duration_dropdown self.lootfenster_option_slider = slider self.loot_summary_option_rows = summary_rows return self:refresh_lootfenster_controls() end local function get_working_nameplate_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.plaketten) ~= "table" then return nil end return working_copy.plaketten end local function apply_nameplate_slider_value(_, slider, value) local settings = get_working_nameplate_settings() if type(settings) ~= "table" then return end local clamped_value = clamp_value(value, slider.minimum, slider.maximum) local rounded_value = round_to_step(clamped_value, slider.step) rounded_value = clamp_value(rounded_value, slider.minimum, slider.maximum) set_slider_visual_value(slider, rounded_value) if settings[slider.key] ~= rounded_value then settings[slider.key] = rounded_value module:mark_options_dirty() end end function module:refresh_nameplate_controls() if not self.nameplate_option_slider or not self.nameplate_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_nameplate_settings() if type(settings) ~= "table" then return false end local scale = settings[nameplate_slider_definition.key] if type(scale) ~= "number" then scale = nameplate_slider_definition.default settings[nameplate_slider_definition.key] = scale end set_slider_visual_value(self.nameplate_option_slider, scale) for index = 1, #nameplate_boolean_order do local key = nameplate_boolean_order[index] local definition = nameplate_boolean_definitions[key] local value = settings[key] if type(value) ~= "boolean" then value = definition.default settings[key] = value end set_boolean_dropdown_visual(self.nameplate_option_rows[key], value) end return true end function module:build_nameplate_options() if self.nameplate_option_slider and self.nameplate_option_rows then return self:refresh_nameplate_controls() end local content_frame = self.content_frames and self.content_frames.plaketten if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Plaketten", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zu den Plaketten") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local section = {} local slider_row, slider = create_slider_row( content_frame, section, nameplate_slider_definition, nameplate_slider_definition.minimum, nameplate_slider_definition.maximum, apply_nameplate_slider_value, palette, designelemente ) slider_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) local previous_row = slider_row for index = 1, #nameplate_boolean_order do local key = nameplate_boolean_order[index] local definition = nameplate_boolean_definitions[key] local row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_nameplate_" .. key .. "_dropdown", definition.label, function() local settings = get_working_nameplate_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_nameplate_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) previous_row = row rows[key] = dropdown_info end self.nameplate_option_slider = slider self.nameplate_option_rows = rows return self:refresh_nameplate_controls() end local function get_working_blizzard_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.blizzard) ~= "table" then return nil end return working_copy.blizzard end function module:refresh_blizzard_controls() if not self.blizzard_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_blizzard_settings() if type(settings) ~= "table" then return false end for index = 1, #blizzard_option_order do local key = blizzard_option_order[index] local definition = blizzard_option_definitions[key] local value = settings[key] if type(value) ~= "boolean" then value = definition.default settings[key] = value end set_boolean_dropdown_visual(self.blizzard_option_rows[key], value) end return true end function module:build_blizzard_options() if self.blizzard_option_rows then return self:refresh_blizzard_controls() end local content_frame = self.content_frames and self.content_frames.system_blizzard if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente local rows = {} local page_heading, page_divider = create_settings_page_header( content_frame, "Blizzard", palette, designelemente ) local general_heading = content_frame:CreateFontString(nil, "OVERLAY") general_heading:SetFontObject(samui_font_12) general_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) general_heading:SetText("Allgemeine Einstellungen zur Blizzard-Oberfläche") general_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", general_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local previous_row local actionbar_divider for index = 1, #blizzard_option_order do if index == 5 then local actionbar_heading = content_frame:CreateFontString(nil, "OVERLAY") actionbar_heading:SetFontObject(samui_font_12) actionbar_heading:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -20 ) actionbar_heading:SetText("Einstellungen zu den Blizzard-Aktionsleisten") actionbar_heading:SetTextColor(palette:get_rgb("text")) actionbar_divider = content_frame:CreateTexture(nil, "ARTWORK") actionbar_divider:SetAtlas("Options_HorizontalDivider", true) actionbar_divider:SetWidth(math.max(content_frame:GetWidth() * 0.90, 1)) actionbar_divider:SetPoint( "TOPLEFT", actionbar_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) previous_row = nil end local key = blizzard_option_order[index] local definition = blizzard_option_definitions[key] local row, dropdown_info = create_boolean_dropdown_row( content_frame, "samui_blizzard_" .. key .. "_dropdown", definition.label, function() local settings = get_working_blizzard_settings() if type(settings) == "table" and type(settings[key]) == "boolean" then return settings[key] end return definition.default end, function(value) local settings = get_working_blizzard_settings() if type(settings) == "table" then settings[key] = value == true end end, palette, designelemente ) if previous_row then row:SetPoint( "TOPLEFT", previous_row, "BOTTOMLEFT", 0, -actionbar_row_gap ) elseif actionbar_divider then row:SetPoint( "TOPLEFT", actionbar_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) else row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) end previous_row = row rows[key] = dropdown_info end self.blizzard_option_rows = rows return self:refresh_blizzard_controls() end local function get_working_samui_elements_settings() local working_copy = ns.options_working_copy if type(working_copy) ~= "table" or type(working_copy.designelemente) ~= "table" then return nil end return working_copy.designelemente end function module:refresh_samui_elements_controls() if not self.samui_elements_option_rows or type(ns.options_working_copy) ~= "table" then return false end local settings = get_working_samui_elements_settings() local designelemente = ns.designelemente if type(settings) ~= "table" or not designelemente then return false end local font_key = settings.font local valid_font = false local font_options = designelemente:get_font_selection_options() for index = 1, #font_options do if font_options[index].value == font_key then valid_font = true break end end if not valid_font then font_key = designelemente:get_default_font_key() settings.font = font_key end set_selection_dropdown_visual(self.samui_elements_option_rows.font, font_key) return true end function module:build_samui_elements_options() if self.samui_elements_option_rows then return self:refresh_samui_elements_controls() end local content_frame = self.content_frames and self.content_frames.system_samui_elements if not content_frame then return false end local palette = ns.palette local designelemente = ns.designelemente if not designelemente then return false end local _, page_divider = create_settings_page_header( content_frame, "SamUI", palette, designelemente ) local font_options = designelemente:get_font_selection_options() local font_row, font_dropdown = create_selection_dropdown_row( content_frame, "samui_system_font_dropdown", "Schriftart verwenden", font_options, function() local settings = get_working_samui_elements_settings() if type(settings) == "table" and type(settings.font) == "string" then return settings.font end return designelemente:get_default_font_key() end, function(value) local settings = get_working_samui_elements_settings() if type(settings) == "table" then settings.font = value end end, palette ) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 font_row:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) self.samui_elements_option_rows = { font = font_dropdown, } return self:refresh_samui_elements_controls() end local function create_profile_overlay( parent, width, height, title_text, palette, designelemente ) local overlay = CreateFrame("Frame", nil, parent) overlay:SetSize(width, height) overlay:SetPoint("CENTER", parent, "CENTER", 0, 0) overlay:SetFrameLevel(parent:GetFrameLevel() + 100) overlay:EnableMouse(true) local background = overlay:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(overlay) background:SetColorTexture(palette:get_surface("panel_solid")) local overlay_border = designelemente:create_border_overlay( overlay, overlay, "frame" ) overlay_border:SetFrameLevel(overlay:GetFrameLevel() + 10) overlay:Hide() local title = overlay:CreateFontString(nil, "OVERLAY") title:SetFontObject(samui_font_12) title:SetPoint("TOP", overlay, "TOP", 0, -14) title:SetText(title_text) title:SetTextColor(palette:get_rgb("gold")) native_close_button_counter = native_close_button_counter + 1 local close_button_name = "samui_options_overlay_close_button_" .. native_close_button_counter local close_button = CreateFrame( "Button", close_button_name, overlay, "UIPanelCloseButton" ) close_button:SetSize(22, 22) close_button:SetPoint("TOPRIGHT", overlay, "TOPRIGHT", -10, -10) close_button:SetScript("OnClick", function() overlay:Hide() end) return overlay, title end local function create_profile_row(parent, palette, designelemente) local row = CreateFrame("Frame", nil, parent) row:SetHeight(profile_row_height) row:SetPoint("RIGHT", parent, "RIGHT", -12, 0) local name_label = row:CreateFontString(nil, "OVERLAY") name_label:SetFontObject(samui_font_12) name_label:SetSize(150, profile_row_height) name_label:SetPoint("LEFT", row, "LEFT", 0, 0) name_label:SetJustifyH("LEFT") name_label:SetTextColor(palette:get_rgb("text")) local select_button = create_standard_button( row, profile_button_width, "Auswählen", palette, designelemente ) select_button.frame:SetPoint("LEFT", name_label, "RIGHT", profile_element_gap, 0) local export_button = create_standard_button( row, profile_button_width, "Exportieren", palette, designelemente ) export_button.frame:SetPoint( "LEFT", select_button.frame, "RIGHT", profile_element_gap, 0 ) local copy_button = create_standard_button( row, profile_button_width, "Kopieren", palette, designelemente ) copy_button.frame:SetPoint( "LEFT", export_button.frame, "RIGHT", profile_element_gap, 0 ) local delete_button = create_standard_button( row, profile_button_width, "Löschen", palette, designelemente ) delete_button.frame:SetPoint( "LEFT", copy_button.frame, "RIGHT", profile_element_gap, 0 ) return { frame = row, name_label = name_label, select_button = select_button, export_button = export_button, copy_button = copy_button, delete_button = delete_button, } end function module:show_profile_export(profile_name) if not self.profile_export_overlay or not self.profile_export_edit_box then return false end local export_string = ns:export_profile(profile_name) if not export_string then return false end self.profile_export_value = export_string self.profile_export_title:SetText("Export: " .. profile_name) self.profile_export_edit_box:SetText(export_string) self.profile_export_overlay:Show() self.profile_export_edit_box:SetFocus() self.profile_export_edit_box:HighlightText() return true end function module:show_profile_copy(profile_name) if not self.profile_copy_overlay then return false end self.profile_copy_source = profile_name self.profile_copy_title:SetText("Kopieren: " .. profile_name) self.profile_copy_name_box:SetText("") self.profile_copy_overlay:Show() self.profile_copy_name_box:SetFocus() return true end function module:refresh_profile_options() if not self.profile_scroll_child or not self.profile_rows then return false end local names = ns:get_profile_names() local palette = ns.palette local designelemente = ns.designelemente local previous_row for index = 1, #names do local profile_name = names[index] local row = self.profile_rows[index] if not row then row = create_profile_row(self.profile_scroll_child, palette, designelemente) self.profile_rows[index] = row end row.frame:ClearAllPoints() if previous_row then row.frame:SetPoint( "TOPLEFT", previous_row.frame, "BOTTOMLEFT", 0, 0 ) row.frame:SetPoint( "TOPRIGHT", previous_row.frame, "BOTTOMRIGHT", 0, 0 ) else row.frame:SetPoint( "TOPLEFT", self.profile_scroll_child, "TOPLEFT", 0, 0 ) row.frame:SetPoint( "TOPRIGHT", self.profile_scroll_child, "TOPRIGHT", -12, 0 ) end row.name_label:SetText(profile_name) local is_active_profile = profile_name == ns:get_active_profile() if is_active_profile then row.select_button.frame:Disable() else row.select_button.frame:Enable() end row.select_button.frame:SetScript("OnClick", function() local success = ns:set_active_profile(profile_name) if success then ReloadUI() end end) row.export_button.frame:SetScript("OnClick", function() module:show_profile_export(profile_name) end) row.copy_button.frame:SetScript("OnClick", function() module:show_profile_copy(profile_name) end) row.delete_button.frame:SetScript("OnClick", function() local success = ns:delete_profile(profile_name) if success then module:refresh_profile_options() end end) if not ns:is_locked_profile(profile_name) and not is_active_profile then row.delete_button.frame:Show() else row.delete_button.frame:Hide() end row.frame:Show() previous_row = row end for index = #names + 1, #self.profile_rows do self.profile_rows[index].frame:Hide() end self.profile_create_heading:ClearAllPoints() if previous_row then self.profile_create_heading:SetPoint( "TOPLEFT", previous_row.frame, "BOTTOMLEFT", 0, -20 ) else self.profile_create_heading:SetPoint( "TOPLEFT", self.profile_scroll_child, "TOPLEFT", 0, 0 ) end self.profile_create_divider:ClearAllPoints() self.profile_create_divider:SetPoint( "TOPLEFT", self.profile_create_heading, "BOTTOMLEFT", self.profile_divider_left_offset, -8 ) self.profile_create_name_box:ClearAllPoints() self.profile_create_name_box:SetPoint( "TOPLEFT", self.profile_create_divider, "BOTTOMLEFT", -self.profile_divider_left_offset, -12 ) self.profile_create_button.frame:ClearAllPoints() self.profile_create_button.frame:SetPoint( "LEFT", self.profile_create_name_box, "RIGHT", profile_element_gap, 0 ) self.profile_import_heading:ClearAllPoints() self.profile_import_heading:SetPoint( "TOPLEFT", self.profile_create_name_box, "BOTTOMLEFT", 0, -20 ) self.profile_import_divider:ClearAllPoints() self.profile_import_divider:SetPoint( "TOPLEFT", self.profile_import_heading, "BOTTOMLEFT", self.profile_divider_left_offset, -8 ) self.profile_import_string_box:ClearAllPoints() self.profile_import_string_box:SetPoint( "TOPLEFT", self.profile_import_divider, "BOTTOMLEFT", -self.profile_divider_left_offset, -12 ) self.profile_import_name_box:ClearAllPoints() self.profile_import_name_box:SetPoint( "TOPLEFT", self.profile_import_string_box, "BOTTOMLEFT", 0, -4 ) self.profile_import_button.frame:ClearAllPoints() self.profile_import_button.frame:SetPoint( "LEFT", self.profile_import_name_box, "RIGHT", profile_element_gap, 0 ) self.profile_reset_heading:ClearAllPoints() self.profile_reset_heading:SetPoint( "TOPLEFT", self.profile_import_name_box, "BOTTOMLEFT", 0, -20 ) self.profile_reset_divider:ClearAllPoints() self.profile_reset_divider:SetPoint( "TOPLEFT", self.profile_reset_heading, "BOTTOMLEFT", self.profile_divider_left_offset, -8 ) self.profile_reset_button.frame:ClearAllPoints() self.profile_reset_button.frame:SetPoint( "TOPLEFT", self.profile_reset_divider, "BOTTOMLEFT", -self.profile_divider_left_offset, -12 ) local dynamic_height = (#names * profile_row_height) + 20 + 12 + 8 + 12 + 22 + 20 + 12 + 8 + 12 + 60 + 4 + 22 + 20 + 12 + 8 + 12 + profile_button_height + 20 self.profile_scroll_child:SetHeight( math.max(profile_scroll_child_minimum_height, dynamic_height) ) return true end function module:build_profile_options() if self.profile_scroll_child then return self:refresh_profile_options() end local content_frame = self.content_frames and self.content_frames.profile if not content_frame or not ns.get_profile_names or not ns.set_active_profile or not ns.export_profile or not ns.copy_profile or not ns.delete_profile or not ns.create_profile or not ns.import_profile or not ns.reset_base_profiles then return false end local palette = ns.palette local designelemente = ns.designelemente local page_heading, page_divider = create_settings_page_header( content_frame, "Profile", palette, designelemente ) local overview_heading = content_frame:CreateFontString(nil, "OVERLAY") overview_heading:SetFontObject(samui_font_12) overview_heading:SetPoint("TOPLEFT", page_heading, "BOTTOMLEFT", 0, -8) overview_heading:SetText("Vorhandene SamUI-Profile verwalten") overview_heading:SetTextColor(palette:get_rgb("text")) local divider_left_offset = (content_frame:GetWidth() - page_divider:GetWidth()) / 2 page_divider:ClearAllPoints() page_divider:SetPoint( "TOPLEFT", overview_heading, "BOTTOMLEFT", divider_left_offset - 12, -8 ) local scroll_frame, scroll_bar = create_minimal_scroll_frame( content_frame, "samui_profile_options_scroll_frame", "samui_profile_options_scroll_bar" ) scroll_bar:SetPoint( "TOPRIGHT", page_divider, "BOTTOMRIGHT", divider_left_offset - 6, -12 ) scroll_bar:SetPoint("BOTTOMRIGHT", content_frame, "BOTTOMRIGHT", -6, 2) scroll_frame:SetPoint( "TOPLEFT", page_divider, "BOTTOMLEFT", 12 - divider_left_offset, -12 ) scroll_frame:SetPoint("BOTTOMRIGHT", scroll_bar, "BOTTOMLEFT", -8, 0) local scroll_child = CreateFrame("Frame", nil, scroll_frame) scroll_child:SetSize(math.max(scroll_frame:GetWidth(), 1), profile_scroll_child_minimum_height) scroll_frame:SetScrollChild(scroll_child) scroll_frame:SetScript("OnSizeChanged", function(_, width) scroll_child:SetWidth(math.max(width, 1)) end) local profile_divider_width = math.max(content_frame:GetWidth() * 0.90, 1) local profile_divider_left_offset = (scroll_child:GetWidth() - profile_divider_width) / 2 local create_heading = scroll_child:CreateFontString(nil, "OVERLAY") create_heading:SetFontObject(samui_font_12) create_heading:SetText("Neues SamUI-Profil erstellen") create_heading:SetTextColor(palette:get_rgb("text")) local create_divider = scroll_child:CreateTexture(nil, "ARTWORK") create_divider:SetAtlas("Options_HorizontalDivider", true) create_divider:SetWidth(profile_divider_width) local create_name_box = create_edit_box( scroll_child, 200, 22, false, palette, designelemente ) local create_button = create_standard_button( scroll_child, 80, "Erstellen", palette, designelemente ) local import_heading = scroll_child:CreateFontString(nil, "OVERLAY") import_heading:SetFontObject(samui_font_12) import_heading:SetText("SamUI-Profil importieren") import_heading:SetTextColor(palette:get_rgb("text")) local import_divider = scroll_child:CreateTexture(nil, "ARTWORK") import_divider:SetAtlas("Options_HorizontalDivider", true) import_divider:SetWidth(profile_divider_width) local import_string_box = create_edit_box( scroll_child, 400, 60, true, palette, designelemente ) local import_name_box = create_edit_box( scroll_child, 200, 22, false, palette, designelemente ) local import_button = create_standard_button( scroll_child, 80, "Importieren", palette, designelemente ) local reset_heading = scroll_child:CreateFontString(nil, "OVERLAY") reset_heading:SetFontObject(samui_font_12) reset_heading:SetText("SamUI-Basisprofile zurücksetzen") reset_heading:SetTextColor(palette:get_rgb("text")) local reset_divider = scroll_child:CreateTexture(nil, "ARTWORK") reset_divider:SetAtlas("Options_HorizontalDivider", true) reset_divider:SetWidth(profile_divider_width) local reset_button = create_standard_button( scroll_child, 120, "Zurücksetzen", palette, designelemente ) self.profile_scroll_frame = scroll_frame self.profile_scroll_bar = scroll_bar self.profile_scroll_child = scroll_child self.profile_rows = {} self.profile_divider_left_offset = profile_divider_left_offset self.profile_create_heading = create_heading self.profile_create_divider = create_divider self.profile_create_name_box = create_name_box self.profile_create_button = create_button self.profile_import_heading = import_heading self.profile_import_divider = import_divider self.profile_import_string_box = import_string_box self.profile_import_name_box = import_name_box self.profile_import_button = import_button self.profile_reset_heading = reset_heading self.profile_reset_divider = reset_divider self.profile_reset_button = reset_button create_button.frame:SetScript("OnClick", function() local name = create_name_box:GetText() local valid, error_message = ns:validate_profile_name(name) if not valid then ns:chat_message(error_message or "Ungültiger Profilname", true) return end local success, create_error_message = ns:create_profile(name) if not success then ns:chat_message(create_error_message or "Profil konnte nicht erstellt werden", true) return end create_name_box:SetText("") module:refresh_profile_options() end) import_button.frame:SetScript("OnClick", function() local success, error_message = ns:import_profile( import_string_box:GetText(), import_name_box:GetText() ) if not success then ns:chat_message(error_message or "Import fehlgeschlagen", true) return end import_string_box:SetText("") import_name_box:SetText("") module:refresh_profile_options() end) reset_button.frame:SetScript("OnClick", function() local success, error_message = ns:reset_base_profiles() if not success then ns:chat_message(error_message or "Profile konnten nicht zurückgesetzt werden", true) return end ReloadUI() end) local export_overlay, export_title = create_profile_overlay( self.options_frame, 520, 120, "Export", palette, designelemente ) local export_edit_box = create_edit_box( export_overlay, 470, 22, false, palette, designelemente ) export_edit_box:SetPoint("CENTER", export_overlay, "CENTER", 0, -10) local restoring_export_text = false export_edit_box:SetScript("OnEditFocusGained", function(edit_box) edit_box:HighlightText() end) export_edit_box:SetScript("OnTextChanged", function(edit_box, user_input) if not user_input or restoring_export_text then return end restoring_export_text = true edit_box:SetText(module.profile_export_value or "") edit_box:HighlightText() restoring_export_text = false end) local copy_overlay, copy_title = create_profile_overlay( self.options_frame, 420, 150, "Kopieren", palette, designelemente ) local copy_name_box = create_edit_box( copy_overlay, 200, 22, false, palette, designelemente ) copy_name_box:SetPoint("TOPLEFT", copy_overlay, "TOPLEFT", 24, -60) local copy_confirm_button = create_standard_button( copy_overlay, 80, "Bestätigen", palette, designelemente ) copy_confirm_button.frame:SetPoint( "LEFT", copy_name_box, "RIGHT", profile_element_gap, 0 ) self.profile_export_overlay = export_overlay self.profile_export_title = export_title self.profile_export_edit_box = export_edit_box self.profile_copy_overlay = copy_overlay self.profile_copy_title = copy_title self.profile_copy_name_box = copy_name_box copy_confirm_button.frame:SetScript("OnClick", function() local new_name = copy_name_box:GetText() local valid, validation_error = ns:validate_profile_name(new_name) if not valid then ns:chat_message(validation_error or "Ungültiger Profilname", true) return end local success, error_message = ns:copy_profile( module.profile_copy_source, new_name ) if not success then ns:chat_message(error_message or "Profil konnte nicht kopiert werden", true) return end copy_overlay:Hide() module:refresh_profile_options() end) return self:refresh_profile_options() end local function create_profile_start_window(module_object, palette, designelemente) local options_frame = module_object.options_frame local dimmer = CreateFrame("Frame", nil, UIParent) dimmer:Hide() dimmer:SetAllPoints(UIParent) dimmer:SetFrameStrata("FULLSCREEN_DIALOG") dimmer:SetFrameLevel(options_frame:GetFrameLevel() + 100) dimmer:EnableMouse(true) local dimmer_background = dimmer:CreateTexture(nil, "BACKGROUND") dimmer_background:SetAllPoints(dimmer) dimmer_background:SetColorTexture( palette:get_rgba("ink", palette:get_alpha("dimmer")) ) local start_window = CreateFrame("Frame", nil, UIParent) start_window:Hide() start_window:SetSize(600, 600) start_window:SetPoint("CENTER", UIParent, "CENTER", 0, 0) start_window:SetFrameStrata("FULLSCREEN_DIALOG") start_window:SetFrameLevel(options_frame:GetFrameLevel() + 110) start_window:EnableMouse(true) local background = start_window:CreateTexture(nil, "BACKGROUND") background:SetAllPoints(start_window) background:SetColorTexture(palette:get_surface("panel_solid")) designelemente:create_border_overlay(start_window, start_window, "frame") local title = start_window:CreateFontString(nil, "OVERLAY") title:SetFontObject(samui_font_20) title:SetPoint("TOP", start_window, "TOP", 0, -38) title:SetText("SamUI " .. ns.version) title:SetTextColor(palette:get_rgb("gold")) local logo = start_window:CreateTexture(nil, "ARTWORK") logo:SetSize(300, 300) logo:SetPoint("TOP", title, "BOTTOM", 0, -28) logo:SetTexture(designelemente:get_texture("logo")) local profile_hint = start_window:CreateFontString(nil, "OVERLAY") profile_hint:SetFontObject(samui_font_12) profile_hint:SetPoint("TOP", logo, "BOTTOM", 0, -6) profile_hint:SetText( "Ein Startprofil wählen, kann in den Einstellungen später angepasst werden" ) profile_hint:SetTextColor(palette:get_rgb("text")) profile_hint:SetJustifyH("CENTER") local dd_button = create_standard_button( start_window, 120, "SamUI-DD", palette, designelemente ) local heal_button = create_standard_button( start_window, 120, "SamUI-Heal", palette, designelemente ) dd_button.frame:SetPoint("TOPRIGHT", logo, "BOTTOM", -6, -28) heal_button.frame:SetPoint("TOPLEFT", logo, "BOTTOM", 6, -28) local start_button = create_standard_button( start_window, 120, "Loslegen", palette, designelemente ) start_button.frame:SetPoint("TOP", logo, "BOTTOM", 0, -72) local dedication_divider = start_window:CreateTexture(nil, "ARTWORK") dedication_divider:SetAtlas("Options_HorizontalDivider", true) dedication_divider:SetWidth(math.max(start_window:GetWidth() * 0.90, 1)) dedication_divider:SetPoint("TOP", start_button.frame, "BOTTOM", 0, -20) local dedication = start_window:CreateFontString(nil, "OVERLAY") dedication:SetFontObject(samui_font_12) dedication:SetPoint("TOP", dedication_divider, "BOTTOM", 0, -20) dedication:SetText("Gewidmet dem besten Freund und treuesten begleiter, Sam") dedication:SetTextColor(palette:get_rgb("text")) dedication:SetJustifyH("CENTER") local function set_selection(profile_name) module_object.start_profile_selection = profile_name if profile_name == "SamUI-DD" then dd_button.frame:LockHighlight() heal_button.frame:UnlockHighlight() else dd_button.frame:UnlockHighlight() heal_button.frame:LockHighlight() end end dd_button.frame:SetScript("OnClick", function() set_selection("SamUI-DD") end) heal_button.frame:SetScript("OnClick", function() set_selection("SamUI-Heal") end) start_button.frame:SetScript("OnClick", function() local success = ns:set_active_profile(module_object.start_profile_selection) if success then ReloadUI() end end) module_object.start_dimmer = dimmer module_object.start_window = start_window module_object.start_dd_button = dd_button module_object.start_heal_button = heal_button module_object.start_action_button = start_button set_selection("SamUI-DD") end function module:show_start_window() if not self.start_window or not self.start_dimmer then return false end self.start_profile_selection = "SamUI-DD" self.start_dd_button.frame:LockHighlight() self.start_heal_button.frame:UnlockHighlight() self.start_dimmer:Show() self.start_window:Show() return true end function ns:show_profile_start_window() return module:show_start_window() end function module:layout_navigation() if not self.navigation_scroll_child or not self.navigation_categories or not self.navigation_items then return false end local top_offset = 0 for definition_index = 1, #navigation_definitions do local definition = navigation_definitions[definition_index] if definition.kind == "item" then local item = self.navigation_items[definition.key] item.frame:ClearAllPoints() item.frame:SetPoint("TOPLEFT", self.navigation_scroll_child, "TOPLEFT", 0, -top_offset) item.frame:SetPoint("TOPRIGHT", self.navigation_scroll_child, "TOPRIGHT", 0, -top_offset) item.frame:Show() top_offset = top_offset + navigation_row_height else local category = self.navigation_categories[definition.key] category.frame:ClearAllPoints() category.frame:SetPoint("TOPLEFT", self.navigation_scroll_child, "TOPLEFT", 0, -top_offset) category.frame:SetPoint("TOPRIGHT", self.navigation_scroll_child, "TOPRIGHT", 0, -top_offset) category.frame:Show() category.toggle_button:SetText(self.navigation_category_state[definition.key] and "-" or "+") top_offset = top_offset + navigation_row_height for child_index = 1, #definition.children do local child_definition = definition.children[child_index] local item = self.navigation_items[child_definition.key] if self.navigation_category_state[definition.key] then item.frame:ClearAllPoints() item.frame:SetPoint( "TOPLEFT", self.navigation_scroll_child, "TOPLEFT", navigation_child_indent, -top_offset ) item.frame:SetPoint("TOPRIGHT", self.navigation_scroll_child, "TOPRIGHT", 0, -top_offset) item.frame:Show() top_offset = top_offset + navigation_row_height else item.frame:Hide() end end end end local visible_height = self.navigation_scroll_frame:GetHeight() or 1 self.navigation_scroll_child:SetHeight(math.max(top_offset, visible_height, 1)) return true end function module:toggle_navigation_category(category_key) if self.navigation_category_state[category_key] == nil then return false end self.navigation_category_state[category_key] = not self.navigation_category_state[category_key] return self:layout_navigation() end function module:select_navigation_item(item_key) local item = self.navigation_items and self.navigation_items[item_key] if not item or not self.content_frames or not self.content_frames[item.content_key] then return false end if item.content_key:match("^unitframes_") then self:build_unitframe_options() elseif item.content_key:match("^leisten_") then self:build_actionbar_options() elseif item.content_key == "minimap" then self:build_minimap_options() elseif item.content_key == "chat" then self:build_chat_options() elseif item.content_key == "verbesserungen" then self:build_verbesserungen_options() elseif item.content_key == "zusatzleisten" then self:build_zusatzleisten_options() elseif item.content_key == "tooltip" then self:build_tooltip_options() elseif item.content_key == "lootfenster" then self:build_lootfenster_options() elseif item.content_key == "plaketten" then self:build_nameplate_options() elseif item.content_key == "profile" then self:build_profile_options() elseif item.content_key == "system_blizzard" then self:build_blizzard_options() elseif item.content_key == "system_samui_elements" then self:build_samui_elements_options() end local palette = ns.palette for content_key, content_frame in pairs(self.content_frames) do if content_key == item.content_key then content_frame:Show() else content_frame:Hide() end end for key, navigation_item in pairs(self.navigation_items) do if key == item_key then navigation_item.label:SetTextColor(palette:get_rgb("gold")) navigation_item.frame:LockHighlight() else navigation_item.label:SetTextColor(palette:get_rgb("text")) navigation_item.frame:UnlockHighlight() end end self.active_navigation_key = item_key return true end function module:reset_navigation() if not self.navigation_category_state then return false end for definition_index = 1, #navigation_definitions do local definition = navigation_definitions[definition_index] if definition.kind == "category" then self.navigation_category_state[definition.key] = false end end self:layout_navigation() return self:select_navigation_item("about") end function module:toggle_window() if not self.options_frame then return end if self.options_frame:IsShown() then self.options_frame:Hide() return end if not self:begin_options_edit() then ns:chat_message("Einstellungen konnten nicht geladen werden", true) return end self:reset_navigation() self.options_frame:ClearAllPoints() self.options_frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) self.options_frame:Show() end local function create_content_frame(parent) local content_frame = CreateFrame("Frame", nil, parent) content_frame:SetAllPoints(parent) content_frame:Hide() return content_frame end local function create_navigation_item(parent, name, label_text, palette, designelemente) local item = CreateFrame("Button", name, parent) item:SetHeight(navigation_row_height) item:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight", "ADD") local label = item:CreateFontString(nil, "OVERLAY") label:SetFontObject(samui_font_12) label:SetPoint("LEFT", item, "LEFT", 4, 0) label:SetPoint("RIGHT", item, "RIGHT", -4, 0) label:SetJustifyH("LEFT") label:SetWordWrap(false) label:SetText(label_text) label:SetTextColor(palette:get_rgb("text")) return item, label end function module:initialize() local palette = ns.palette local designelemente = ns.designelemente local border_offset = designelemente:get_border_offset("frame") local body_gap = 5 if type(ns.register_profile_provider) == "function" then ns:register_profile_provider( "aktionsleisten", collect_actionbar_profile_data, apply_actionbar_profile_data ) ns:register_profile_provider( "blizzard", collect_blizzard_profile_data, apply_blizzard_profile_data ) end ns:register_event("ADDON_LOADED", self, handle_blizzard_visibility_event) ns:register_event("PLAYER_ENTERING_WORLD", self, handle_blizzard_visibility_event) ns:register_event("GROUP_ROSTER_UPDATE", self, handle_blizzard_visibility_event) ns:register_event("PLAYER_REGEN_ENABLED", self, handle_blizzard_visibility_event) ns:register_event("NAME_PLATE_UNIT_ADDED", self, handle_blizzard_visibility_event) local options_frame = CreateFrame("Frame", nil, UIParent) options_frame:SetSize(760, 760) options_frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) options_frame:SetFrameStrata("FULLSCREEN_DIALOG") options_frame:SetMovable(true) options_frame:EnableMouse(true) options_frame:RegisterForDrag("LeftButton") options_frame:SetClampedToScreen(false) options_frame:SetScript("OnDragStart", function(frame) frame:StartMoving() end) options_frame:SetScript("OnDragStop", function(frame) frame:StopMovingOrSizing() end) options_frame:Hide() local title_box, title_border = create_panel(options_frame, "button_deep", palette, designelemente) title_box:SetPoint("TOPLEFT", options_frame, "TOPLEFT", border_offset, -border_offset) title_box:SetPoint("TOPRIGHT", options_frame, "TOPRIGHT", -border_offset, -border_offset) title_box:SetHeight(28) local title = title_box:CreateFontString(nil, "OVERLAY") title:SetPoint("CENTER", title_box, "CENTER", 0, 0) title:SetFontObject(samui_font_12) title:SetText("SamUI " .. ns.version) title:SetTextColor(palette:get_rgb("gold")) local close_button = CreateFrame( "Button", "samui_options_close_button", options_frame, "UIPanelCloseButton" ) close_button:SetSize(22, 22) close_button:SetPoint("RIGHT", title_box, "RIGHT", border_offset - 10, 0) close_button:SetFrameLevel(title_border:GetFrameLevel() + 1) close_button:SetScript("OnClick", function() options_frame:Hide() end) local body_box, body_border = create_panel(options_frame, "panel_solid", palette, designelemente) body_box:SetPoint( "TOPLEFT", title_box, "BOTTOMLEFT", 0, -(border_offset * 2 + body_gap) ) body_box:SetPoint("BOTTOMRIGHT", options_frame, "BOTTOMRIGHT", -border_offset, border_offset) local toolbar = CreateFrame("Frame", nil, body_box) toolbar:SetPoint("TOPLEFT", body_box, "TOPLEFT", 8, -8) toolbar:SetPoint("TOPRIGHT", body_box, "TOPRIGHT", -8, -8) toolbar:SetHeight(26) local save_button = create_standard_button(toolbar, 150, "Einstellungen speichern") save_button.frame:Disable() local exit_button = create_standard_button(toolbar, 80, "Beenden") if save_button.label then save_button.label:SetFontObject(samui_font_12) save_button.frame:SetWidth(math.max(22, save_button.label:GetStringWidth() + 24)) end if exit_button.label then exit_button.label:SetFontObject(samui_font_12) exit_button.frame:SetWidth(math.max(22, exit_button.label:GetStringWidth() + 24)) end exit_button.frame:SetPoint("RIGHT", toolbar, "RIGHT", 0, 0) save_button.frame:SetPoint("RIGHT", exit_button.frame, "LEFT", -6, 0) local inner_frame_texture = body_box:CreateTexture(nil, "OVERLAY", nil, 2) inner_frame_texture:SetPoint("TOPLEFT", toolbar, "BOTTOMLEFT", 0, -8) inner_frame_texture:SetPoint("BOTTOMRIGHT", body_box, "BOTTOMRIGHT", -8, 8) inner_frame_texture:SetAtlas("Options_InnerFrame", false) local navigation_panel = CreateFrame( "Frame", "samui_options_navigation_panel", body_box ) navigation_panel:SetPoint("TOPLEFT", toolbar, "BOTTOMLEFT", 0, -8) navigation_panel:SetPoint("BOTTOMLEFT", body_box, "BOTTOMLEFT", 8, 8) navigation_panel:SetWidth(navigation_width) local navigation_scroll_frame, navigation_scroll_bar = create_minimal_scroll_frame( navigation_panel, "samui_options_navigation_scroll_frame", "samui_options_navigation_scroll_bar" ) navigation_scroll_bar:SetPoint("TOPRIGHT", navigation_panel, "TOPRIGHT", -8, -8) navigation_scroll_bar:SetPoint("BOTTOMRIGHT", navigation_panel, "BOTTOMRIGHT", -8, 8) navigation_scroll_frame:SetPoint("TOPLEFT", navigation_panel, "TOPLEFT", 6, -6) navigation_scroll_frame:SetPoint( "BOTTOMRIGHT", navigation_scroll_bar, "BOTTOMLEFT", -8, 0 ) local navigation_scroll_child = CreateFrame("Frame", nil, navigation_scroll_frame) navigation_scroll_child:SetSize(math.max(navigation_scroll_frame:GetWidth(), 1), 1) navigation_scroll_frame:SetScrollChild(navigation_scroll_child) navigation_scroll_frame:SetScript("OnSizeChanged", function(_, width) navigation_scroll_child:SetWidth(math.max(width, 1)) module:layout_navigation() end) local details_panel = CreateFrame( "Frame", "samui_options_details_panel", body_box ) details_panel:SetPoint("TOPLEFT", toolbar, "BOTTOMLEFT", navigation_width + 8, -8) details_panel:SetPoint("BOTTOMRIGHT", body_box, "BOTTOMRIGHT", -8, 8) local content_area = CreateFrame("Frame", nil, details_panel) content_area:SetPoint("TOPLEFT", details_panel, "TOPLEFT", 8, -8) content_area:SetPoint("BOTTOMRIGHT", details_panel, "BOTTOMRIGHT", -8, 8) local content_frames = {} content_frames.about = create_content_frame(content_area) local unitframe_content_frames = {} for index = 1, #unitframe_option_order do local unit = unitframe_option_order[index] local content_key = "unitframes_" .. unit local content_frame = create_content_frame(content_area) content_frames[content_key] = content_frame unitframe_content_frames[unit] = content_frame end local actionbar_content_frames = {} local actionbar_general_content_frame = create_content_frame(content_area) content_frames.leisten_allgemein = actionbar_general_content_frame actionbar_content_frames.allgemein = actionbar_general_content_frame for index = 1, #actionbar_option_order do local bar_key = actionbar_option_order[index] local content_key = "leisten_" .. bar_key local content_frame = create_content_frame(content_area) content_frames[content_key] = content_frame actionbar_content_frames[bar_key] = content_frame end content_frames.minimap = create_content_frame(content_area) content_frames.verbesserungen = create_content_frame(content_area) content_frames.zusatzleisten = create_content_frame(content_area) content_frames.tooltip = create_content_frame(content_area) content_frames.lootfenster = create_content_frame(content_area) content_frames.plaketten = create_content_frame(content_area) content_frames.chat = create_content_frame(content_area) content_frames.profile = create_content_frame(content_area) content_frames.system_blizzard = create_content_frame(content_area) content_frames.system_samui_elements = create_content_frame(content_area) local about_block = CreateFrame("Frame", nil, content_frames.about) about_block:SetSize(440, 370) about_block:SetPoint("CENTER", content_frames.about, "CENTER", 0, 0) local about_heading = about_block:CreateFontString(nil, "OVERLAY") about_heading:SetFontObject(samui_font_18) about_heading:SetSize(440, 20) about_heading:SetPoint("TOP", about_block, "TOP", 0, 0) about_heading:SetText("SamUI " .. ns.version) about_heading:SetTextColor(palette:get_rgb("gold")) local about_logo = about_block:CreateTexture(nil, "ARTWORK") about_logo:SetSize(300, 300) about_logo:SetPoint("TOP", about_heading, "BOTTOM", 0, -20) about_logo:SetTexture(designelemente:get_texture("logo")) local about_text = about_block:CreateFontString(nil, "OVERLAY") about_text:SetFontObject(samui_font_12) about_text:SetSize(440, 20) about_text:SetPoint("TOP", about_logo, "BOTTOM", 0, -10) about_text:SetJustifyH("CENTER") about_text:SetText("Gewidmet dem besten Freund und treuesten Begleiter, Sam") about_text:SetTextColor(palette:get_rgb("text")) local navigation_categories = {} local navigation_items = {} local navigation_category_state = {} for definition_index = 1, #navigation_definitions do local definition = navigation_definitions[definition_index] if definition.kind == "item" then local item, label = create_navigation_item( navigation_scroll_child, "samui_options_tree_item_" .. definition.key, definition.label, palette, designelemente ) local item_key = definition.key item:SetScript("OnClick", function() module:select_navigation_item(item_key) end) navigation_items[definition.key] = { frame = item, label = label, content_key = definition.content_key, } else navigation_category_state[definition.key] = false local category = CreateFrame( "Button", "samui_options_tree_category_" .. definition.key, navigation_scroll_child ) category:SetHeight(navigation_row_height) local toggle_button = CreateFrame( "Button", "samui_options_tree_toggle_" .. definition.key, category, "UIPanelButtonTemplate" ) toggle_button:SetSize(14, 14) toggle_button:SetPoint("LEFT", category, "LEFT", 0, 0) local toggle_label = apply_standard_button_font(toggle_button, samui_font_10) toggle_button:SetText("+") local category_label = category:CreateFontString(nil, "OVERLAY") category_label:SetFontObject(samui_font_12) category_label:SetPoint("LEFT", toggle_button, "RIGHT", 4, 0) category_label:SetPoint("RIGHT", category, "RIGHT", -4, 0) category_label:SetJustifyH("LEFT") category_label:SetWordWrap(false) category_label:SetText(definition.label) category_label:SetTextColor(palette:get_rgb("gold")) local category_key = definition.key local function toggle_category() module:toggle_navigation_category(category_key) end category:SetScript("OnClick", toggle_category) toggle_button:SetScript("OnClick", toggle_category) navigation_categories[definition.key] = { frame = category, label = category_label, toggle_button = toggle_button, } for child_index = 1, #definition.children do local child_definition = definition.children[child_index] local item, label = create_navigation_item( navigation_scroll_child, "samui_options_tree_item_" .. child_definition.key, child_definition.label, palette, designelemente ) local child_item_key = child_definition.key item:SetScript("OnClick", function() module:select_navigation_item(child_item_key) end) item:Hide() navigation_items[child_definition.key] = { frame = item, label = label, content_key = child_definition.content_key, } end end end self.options_frame = options_frame self.title_box = title_box self.title_border = title_border self.body_box = body_box self.body_border = body_border self.toolbar = toolbar self.options_save_button = save_button self.options_exit_button = exit_button self.inner_frame_texture = inner_frame_texture self.navigation_panel = navigation_panel self.navigation_scroll_frame = navigation_scroll_frame self.navigation_scroll_bar = navigation_scroll_bar self.navigation_scroll_child = navigation_scroll_child self.navigation_categories = navigation_categories self.navigation_items = navigation_items self.navigation_category_state = navigation_category_state self.details_panel = details_panel self.content_area = content_area self.content_frames = content_frames self.unitframe_content_frames = unitframe_content_frames self.actionbar_content_frames = actionbar_content_frames self.options_dirty = false save_button.frame:SetScript("OnClick", function() module:save_options_working_copy() end) exit_button.frame:SetScript("OnClick", function() options_frame:Hide() end) options_frame:SetScript("OnHide", function() module:discard_options_working_copy() end) self:layout_navigation() self:select_navigation_item("about") create_profile_start_window(self, palette, designelemente) SLASH_SAMUI1 = "/samui" SlashCmdList["SAMUI"] = function() module:toggle_window() end end ns:register_initial_module("installer")