From ac01e4474c82f5f505e508a0680eb99c32a65ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 15:42:52 +0100 Subject: [PATCH 1/5] Setup for redesign feature --- AltSystem.toc | 2 +- docs/4-redesign.md | 12 ++++++++++++ docs/Changelog.md | 3 --- docs/roll_tab_design.png | Bin 0 -> 71752 bytes package.sh | 2 +- 5 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 docs/4-redesign.md create mode 100644 docs/roll_tab_design.png diff --git a/AltSystem.toc b/AltSystem.toc index e541ac4..a085d34 100644 --- a/AltSystem.toc +++ b/AltSystem.toc @@ -2,7 +2,7 @@ ## Title: AltSystem ## Notes: Enhances RP gameplay with a custom rolling system ## Author: Rukira -## Version: 1.1 +## Version: 2.0 ## Dependencies: totalRP3, totalRP3_Extended ## SavedVariables: AltSystemDB diff --git a/docs/4-redesign.md b/docs/4-redesign.md new file mode 100644 index 0000000..4a11115 --- /dev/null +++ b/docs/4-redesign.md @@ -0,0 +1,12 @@ +# Feature: Major Redesign +This redesign of the Addon's window will start by following the following [design](./roll_tab_design.png) + +- The window will be a tabbed window +- The 'Use Skills' tab will correspond to the current roll screen +- The 'Build Skills' tab will be a new screen, to be implemented later. For now, leave it empty. +- The 'Log' should record all rolls made by the user, and be displayed in a scrollable list. + - They should be displayed in the same style as the announced rolls, even when the announce option is off +- The log should store a maximum of 100 rolls + +## Implementation details +TODO \ No newline at end of file diff --git a/docs/Changelog.md b/docs/Changelog.md index dd05c6a..e69de29 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,3 +0,0 @@ -- Adding default 'Unskilled (-4)' option -- The roll window now remembers all user selections (skill, item, armor type, shield, pet, announce and channel) across sessions -- Fixed an issue where the wrong rolls were displayed when in a large enough group \ No newline at end of file diff --git a/docs/roll_tab_design.png b/docs/roll_tab_design.png new file mode 100644 index 0000000000000000000000000000000000000000..5754915dd6627db6dca2b293a92cc0fa9a70050b GIT binary patch literal 71752 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 Date: Tue, 12 May 2026 15:45:33 +0100 Subject: [PATCH 2/5] Plan of action --- docs/4-redesign.md | 70 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/docs/4-redesign.md b/docs/4-redesign.md index 4a11115..fcd504c 100644 --- a/docs/4-redesign.md +++ b/docs/4-redesign.md @@ -8,5 +8,71 @@ This redesign of the Addon's window will start by following the following [desig - They should be displayed in the same style as the announced rolls, even when the announce option is off - The log should store a maximum of 100 rolls -## Implementation details -TODO \ No newline at end of file +## Implementation plan + +### 1. Restructure the main window layout (UI.lua) +- Increase `WINDOW_WIDTH` to roughly double (≈660) to accommodate the two-column layout (left panel for tabs, right panel for the log) +- Replace `BasicFrameTemplateWithInset` or layer a new structure inside it: + - **Left column (~50% width):** contains two tabs ("Use Skills", "Build Skills") and their content panels + - **Right column (~50% width):** contains the "Log" header and a scrollable log list +- Keep the title bar ("AltSystem") and close button at the top spanning the full width + +### 2. Implement the tab system (UI.lua) +- Create two tab buttons ("Use Skills" and "Build Skills") anchored at the top of the left column +- Use `PanelTemplates_SetNumTabs` / `PanelTemplates_SetTab` or manual highlight toggling to switch active tab styling +- **"Use Skills" tab content:** migrate all existing UI elements (Roll Type radios → Skill dropdown → Armor radios → Modifiers checkboxes → Announce dropdown → Roll button) into this tab's content frame + - Adapt the current layout from `CreateMainFrame` — re-parent all widgets to the tab content frame instead of `f` directly +- **"Build Skills" tab content:** create an empty placeholder frame (can show a "Coming soon" label) +- Toggling tabs shows/hides the corresponding content frame + +### 3. Redesign the "Use Skills" tab to match the mockup (UI.lua) +- Replace the current separate Attack/Defense buttons with a **Roll Type** radio-button group ("Attack Roll" / "Defense Roll") that sets `AltSystem.State.rollType` +- Keep the **Skill** dropdown as-is (already matches the mockup) +- Replace the current Defense dropdown with an **Armor** radio-button group ("No Armor" / "Basic Armor (+1)" / "Heavy Armor (+2)") + - Map these to the existing `AltSystem.Data.Defenses` entries; show armor options only when Defense Roll is selected, or always visible per mockup +- Group **Shield** and **Pet** checkboxes under a "Modifiers (optional)" section header with a "Label" sub-header matching the mockup +- Replace the Announce checkbox + channel dropdown with a single **"Announce Roll"** dropdown whose options are "Self Roll" (no announce) plus the existing channel list (Emote, Party, Raid, Guild) + - "Self Roll" maps to `announceEnabled = false`; any other selection maps to `announceEnabled = true` with the corresponding channel index +- Add a single **"Roll $rollType"** button at the bottom (text dynamically reflects "Roll Attack" or "Roll Defense") +- Remove the old roll-result text area from this tab (results now go to the Log panel) + +### 4. Build the Log panel (UI.lua) +- Create a right-side panel with a "Log" header label +- Inside, create a `ScrollFrame` (using `UIPanelScrollFrameTemplate` or a manual scroll child) to hold log entries +- Each log entry is a small frame/fontstring displaying the roll result in the same format as the announced message: + - `"[Name] rolled [d20 result] [modifiers] = [total]"` (reuse `BuildModifierString` from Roll.lua) + - Critical rolls show "rolled a Critical Failure!" or "rolled a Critical Success!" +- Entries are listed newest-first (most recent at top) in a vertically stacked layout + +### 5. Implement the roll log data store (Core.lua / Roll.lua) +- Add `AltSystem.State.rollLog = {}` — an array of log entry tables, each containing: `{ text = "...", timestamp = time() }` +- In `Roll.lua`, after every roll result is calculated (in `CalculateAndDisplayResult`), build the log message string (same format as announce) and insert it into `AltSystem.State.rollLog` +- Cap the log at **100 entries**: if `#rollLog > 100`, remove the oldest entry (`table.remove(rollLog, 1)`) +- After inserting, call a UI refresh function to update the scroll frame content +- The log is **always populated**, regardless of the announce setting (per the requirement: "displayed in the same style as the announced rolls, even when the announce option is off") +- Log does **not** need to persist across sessions (not mentioned in requirements); keep it in memory only + +### 6. Wire up the new Roll button (Roll.lua) +- The single "Roll $rollType" button calls `AltSystem:PerformRoll(state.rollType)` where `state.rollType` is set by the radio-button group ("attack" or "defense") +- Existing `PerformRoll` and `CalculateAndDisplayResult` logic remains largely unchanged; only the final display step changes from setting `ResultText` to appending to the log + refreshing the log UI + +### 7. Update state persistence (Core.lua) +- Save/restore `rollType` selection (attack/defense) in `AltSystemDB` +- Update announce state handling to work with the new single-dropdown approach (save selected option index) +- Armor selection (radio group) replaces `selectedDefenseIndex` — reuse same key or migrate + +### 8. Update the .toc file if needed (AltSystem.toc) +- No new Lua files are expected (all changes fit in existing files), but verify the load order is still correct + +### 9. Testing checklist +- [ ] Window opens at new size, tabs switch correctly +- [ ] "Use Skills" tab shows all controls matching the mockup layout +- [ ] "Build Skills" tab is empty / shows placeholder +- [ ] Attack and Defense rolls work correctly via the new single Roll button +- [ ] Log panel populates with each roll, formatted like announce messages +- [ ] Log scrolls when entries exceed visible area +- [ ] Log caps at 100 entries, oldest removed first +- [ ] Log populates even when announce is set to "Self Roll" (off) +- [ ] Announce still works when a channel is selected +- [ ] State (roll type, armor, announce option) persists across sessions +- [ ] Window is draggable and clamps to screen \ No newline at end of file From ffc693a63ce1571ba03affc2864bed04b8f1436a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 16:20:52 +0100 Subject: [PATCH 3/5] Roll tab --- Core.lua | 37 +++- Data.lua | 8 +- Roll.lua | 105 ++++++---- UI.lua | 578 +++++++++++++++++++++++++++++++++++++------------------ 4 files changed, 487 insertions(+), 241 deletions(-) diff --git a/Core.lua b/Core.lua index a3d1ab9..865bf63 100644 --- a/Core.lua +++ b/Core.lua @@ -6,11 +6,14 @@ AltSystem.State = { selectedSkillIndex = 1, selectedSkillName = nil, -- skill name used to restore selection across sessions selectedItemIndex = 1, -- 1 = No item - selectedDefenseIndex = 1, -- 1 = Base armor + selectedDefenseIndex = 1, -- 1 = No Armor shieldEnabled = false, petSummonEnabled = false, announceEnabled = false, announceChannelIndex = 1, -- 1 = Emote, 2 = Party, 3 = Raid, 4 = Guild + rollType = "attack", -- "attack" or "defense" + announceOptionIndex = 1, -- 1 = Self Roll, 2+ = channel from AnnounceChannels + rollLog = {}, -- array of { text = "...", timestamp = time() }, max 100, not persisted } -- Channel definitions for announcing rolls @@ -35,12 +38,6 @@ end) function AltSystem:Init() -- Load saved settings AltSystemDB = AltSystemDB or {} - if AltSystemDB.announceEnabled ~= nil then - AltSystem.State.announceEnabled = AltSystemDB.announceEnabled - end - if AltSystemDB.announceChannelIndex then - AltSystem.State.announceChannelIndex = AltSystemDB.announceChannelIndex - end if AltSystemDB.petSummonEnabled ~= nil then AltSystem.State.petSummonEnabled = AltSystemDB.petSummonEnabled end @@ -56,6 +53,28 @@ function AltSystem:Init() if AltSystemDB.selectedSkillName then AltSystem.State.selectedSkillName = AltSystemDB.selectedSkillName end + if AltSystemDB.rollType then + AltSystem.State.rollType = AltSystemDB.rollType + end + -- Migrate from old announce settings or load new announceOptionIndex + if AltSystemDB.announceOptionIndex then + AltSystem.State.announceOptionIndex = AltSystemDB.announceOptionIndex + elseif AltSystemDB.announceEnabled ~= nil then + -- Backwards compat: derive from old announceEnabled + announceChannelIndex + if AltSystemDB.announceEnabled and AltSystemDB.announceChannelIndex then + AltSystem.State.announceOptionIndex = AltSystemDB.announceChannelIndex + 1 + else + AltSystem.State.announceOptionIndex = 1 + end + end + -- Derive announceEnabled and announceChannelIndex from announceOptionIndex + if AltSystem.State.announceOptionIndex > 1 then + AltSystem.State.announceEnabled = true + AltSystem.State.announceChannelIndex = AltSystem.State.announceOptionIndex - 1 + else + AltSystem.State.announceEnabled = false + AltSystem.State.announceChannelIndex = 1 + end -- Register slash command /altsystem SLASH_ALTSYSTEM1 = "/altsystem" @@ -68,13 +87,13 @@ function AltSystem:Init() saveFrame:RegisterEvent("PLAYER_LOGOUT") saveFrame:SetScript("OnEvent", function() AltSystemDB = AltSystemDB or {} - AltSystemDB.announceEnabled = AltSystem.State.announceEnabled - AltSystemDB.announceChannelIndex = AltSystem.State.announceChannelIndex + AltSystemDB.announceOptionIndex = AltSystem.State.announceOptionIndex AltSystemDB.petSummonEnabled = AltSystem.State.petSummonEnabled AltSystemDB.shieldEnabled = AltSystem.State.shieldEnabled AltSystemDB.selectedItemIndex = AltSystem.State.selectedItemIndex AltSystemDB.selectedDefenseIndex = AltSystem.State.selectedDefenseIndex AltSystemDB.selectedSkillName = AltSystem.State.selectedSkillName + AltSystemDB.rollType = AltSystem.State.rollType end) -- Build the main frame now that saved variables are loaded diff --git a/Data.lua b/Data.lua index e46d887..d1e5f56 100644 --- a/Data.lua +++ b/Data.lua @@ -147,11 +147,11 @@ AltSystem.Data.Items = { { name = "Epic item", modifier = 5 }, } --- Defense options: name and modifier +-- Defense / Armor options: name and modifier AltSystem.Data.Defenses = { - { name = "Base armor", modifier = 0 }, - { name = "Extra small armor", modifier = 1 }, - { name = "Extra large armor", modifier = 2 }, + { name = "None", modifier = 0 }, + { name = "Partial", modifier = 1 }, + { name = "Full", modifier = 2 }, } -- Shield modifier diff --git a/Roll.lua b/Roll.lua index a37ca8b..29d9190 100644 --- a/Roll.lua +++ b/Roll.lua @@ -27,10 +27,42 @@ local function BuildModifierString(modifiers) return table.concat(parts, " ") end +-- Build the log message string (always uses the non-emote format with character name) +local function BuildLogMessage(rollValue, modifiers, total) + local name = GetCharacterName() + local modStr = BuildModifierString(modifiers) + if modStr ~= "" then + return name .. " rolled " .. rollValue .. " " .. modStr .. " = " .. total + else + return name .. " rolled " .. rollValue .. " = " .. total + end +end + +-- Build the critical roll log message +local function BuildCriticalLogMessage(isCriticalSuccess) + local name = GetCharacterName() + if isCriticalSuccess then + return name .. " rolled a Critical Success!" + else + return name .. " rolled a Critical Failure!" + end +end + +-- Add an entry to the roll log and refresh the UI +local function AddLogEntry(text) + local rollLog = AltSystem.State.rollLog + table.insert(rollLog, { text = text, timestamp = time() }) + -- Cap at 100 entries + if #rollLog > 100 then + table.remove(rollLog, 1) + end + -- Refresh the log panel if it exists + if AltSystem.RefreshLogPanel then + AltSystem:RefreshLogPanel() + end +end + -- Send a message to the given channel. --- Note: EMOTE channel uses SendChatMessage like all others. The WoW API --- SendChatMessage(msg, "EMOTE") sends a proper /e emote from any context, --- unlike RunMacroText which is a protected function requiring a hardware event. local function SendToChannel(msg, channel) SendChatMessage(msg, channel) end @@ -64,6 +96,24 @@ local function AnnounceRoll(rollValue, modifiers, total) SendToChannel(msg, channelDef.channel) end +-- Announce a critical roll result +local function AnnounceCritical(isCriticalSuccess) + if not AltSystem.State.announceEnabled then return end + + local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] + if not channelDef then return end + + local critText = isCriticalSuccess and "rolled a Critical Success!" or "rolled a Critical Failure!" + local msg + if channelDef.channel == "EMOTE" then + msg = critText + else + msg = GetCharacterName() .. " " .. critText + end + + SendToChannel(msg, channelDef.channel) +end + -- Perform a roll: triggers the WoW native /roll 20 command function AltSystem:PerformRoll(rollType) pendingPetRoll = nil @@ -116,32 +166,12 @@ end) function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) -- Critical rolls bypass normal calculation if rollValue == 1 then - if AltSystem.ResultText then - AltSystem.ResultText:SetText("|cffff0000Critical Failure|r") - end - if AltSystem.State.announceEnabled then - local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] - if channelDef then - local critMsg = channelDef.channel == "EMOTE" - and "rolled a Critical Failure!" - or (GetCharacterName() .. " rolled a Critical Failure!") - SendToChannel(critMsg, channelDef.channel) - end - end + AddLogEntry(BuildCriticalLogMessage(false)) + AnnounceCritical(false) return elseif rollValue == 20 then - if AltSystem.ResultText then - AltSystem.ResultText:SetText("|cff00ff00Critical Success|r") - end - if AltSystem.State.announceEnabled then - local channelDef = AltSystem.AnnounceChannels[AltSystem.State.announceChannelIndex] - if channelDef then - local critMsg = channelDef.channel == "EMOTE" - and "rolled a Critical Success!" - or (GetCharacterName() .. " rolled a Critical Success!") - SendToChannel(critMsg, channelDef.channel) - end - end + AddLogEntry(BuildCriticalLogMessage(true)) + AnnounceCritical(true) return end @@ -153,7 +183,6 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) local itemMod = item and item.modifier or 0 local total = rollValue - local breakdown = "Roll: " .. rollValue local modifiers = {} @@ -166,18 +195,14 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) total = rollValue + skillMod + itemMod + petMod if not isBaseRoll then - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) end if itemMod ~= 0 then - breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) end if petMod ~= 0 then - breakdown = breakdown .. " | Pet: +" .. petMod table.insert(modifiers, { name = "Pet", value = petMod }) end - breakdown = breakdown .. "\n|cffffd100Attack Total: " .. total .. "|r" elseif rollType == "defense" then -- Defense Roll = roll + skill modifier + item modifier + defense modifier + shield modifier @@ -188,30 +213,26 @@ function AltSystem:CalculateAndDisplayResult(rollType, rollValue, petRollValue) total = rollValue + skillMod + itemMod + defenseMod + shieldMod + petMod if not isBaseRoll then - breakdown = breakdown .. "\nSkill: " .. FormatModifier(skillMod) table.insert(modifiers, { name = skill and skill.name or "Skill", value = skillMod }) end if itemMod ~= 0 then - breakdown = breakdown .. " | Item: " .. FormatModifier(itemMod) table.insert(modifiers, { name = item and item.name or "Item", value = itemMod }) end - breakdown = breakdown .. "\nDefense: " .. FormatModifier(defenseMod) - table.insert(modifiers, { name = defense and defense.name or "Defense", value = defenseMod }) + if defenseMod ~= 0 then + table.insert(modifiers, { name = defense and defense.name or "Armor", value = defenseMod }) + end if shieldMod ~= 0 then - breakdown = breakdown .. " | Shield: " .. FormatModifier(shieldMod) table.insert(modifiers, { name = "Shield", value = shieldMod }) end if petMod ~= 0 then - breakdown = breakdown .. " | Pet: +" .. petMod table.insert(modifiers, { name = "Pet", value = petMod }) end - breakdown = breakdown .. "\n|cff00ccffDefense Total: " .. total .. "|r" end - if AltSystem.ResultText then - AltSystem.ResultText:SetText(breakdown) - end + -- Add to log (always, regardless of announce setting) + AddLogEntry(BuildLogMessage(rollValue, modifiers, total)) + -- Announce to chat (if enabled) AnnounceRoll(rollValue, modifiers, total) end diff --git a/UI.lua b/UI.lua index 5162da2..fdbdace 100644 --- a/UI.lua +++ b/UI.lua @@ -1,14 +1,17 @@ -- AltSystem UI --- Creates the main dialog window with all interface elements. +-- Creates the main dialog window with tabbed layout. +-- The "Use Skills" tab contains controls on the left and a Log panel on the right. -- Uses the modern DropdownButton API (WoW 10.2.5+ / 12.0+). AltSystem = AltSystem or {} -local WINDOW_WIDTH = 300 -local WINDOW_HEIGHT = 478 -local PADDING = 12 -local ROW_HEIGHT = 30 -local LABEL_WIDTH = 80 +local WINDOW_WIDTH = 726 +local WINDOW_HEIGHT = 500 +local CONTROLS_WIDTH = 363 +local LOG_WIDTH = 363 +local PADDING = 12 +local ROW_HEIGHT = 26 +local SECTION_GAP = 10 -- Helper: Build the skill option list from current AltSystem.Data.Skills local function BuildSkillOptions() @@ -27,38 +30,98 @@ local function BuildSkillOptions() end -- Helper: Create a modern dropdown (WowStyle1DropdownTemplate) -local function CreateDropdown(parent, name, yOffset, labelText, options, defaultIndex, onSelect) - local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal") - label:SetPoint("TOPLEFT", parent, "TOPLEFT", PADDING, yOffset) - label:SetText(labelText) - label:SetWidth(LABEL_WIDTH) - label:SetJustifyH("LEFT") +local function CreateDropdown(parent, name, labelText, options, defaultIndex, onSelect, labelFont) + local container = CreateFrame("Frame", nil, parent) + container:SetHeight(ROW_HEIGHT) + + local label + if labelText and labelText ~= "" then + label = container:CreateFontString(nil, "OVERLAY", labelFont or "GameFontNormal") + label:SetPoint("LEFT", container, "LEFT", 0, 0) + label:SetText(labelText) + label:SetJustifyH("LEFT") + end local selectedIndex = defaultIndex or 1 - local dropdown = CreateFrame("DropdownButton", name, parent, "WowStyle1DropdownTemplate") - dropdown:SetPoint("LEFT", label, "RIGHT", 4, 0) - dropdown:SetWidth(160) + local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") + if label then + dropdown:SetPoint("LEFT", label, "RIGHT", 8, 0) + else + dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) + end + dropdown:SetWidth(180) dropdown:SetupMenu(function(dropdown, rootDescription) for i, option in ipairs(options) do rootDescription:CreateRadio( - option.text, - function(data) return data == selectedIndex end, - function(data) - selectedIndex = data - if onSelect then onSelect(data, options[data]) end - end, - i + option.text, + function(data) + return data == selectedIndex + end, + function(data) + selectedIndex = data + if onSelect then + onSelect(data, options[data]) + end + end, + i ) end end) - return dropdown, function() return selectedIndex end, function(idx) selectedIndex = idx end + return container, dropdown, function() + return selectedIndex + end, function(idx) + selectedIndex = idx + end +end + +-- Helper: Create a radio button (CheckButton with radio texture) +local function CreateRadioButton(parent, name, text, x, y, isChecked, onClick) + local radio = CreateFrame("CheckButton", name, parent, "UIRadioButtonTemplate") + radio:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) + radio:SetChecked(isChecked) + + local radioText = radio:GetFontString() + if radioText then + radioText:SetText(text) + radioText:SetFontObject("GameFontHighlight") + else + local t = radio:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + t:SetPoint("LEFT", radio, "RIGHT", 4, 0) + t:SetText(text) + end + + radio:SetScript("OnClick", function(self) + onClick(self) + end) + + return radio +end + +-- Helper: Create a section header (golden text) +local function CreateSectionHeader(parent, text, x, y) + local header = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge") + header:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) + header:SetText(text) + header:SetTextColor(0.9, 0.75, 0.2) + return header +end + +-- Helper: Create a sub-label (smaller golden text) +local function CreateSubLabel(parent, text, x, y) + local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) + label:SetText(text) + label:SetTextColor(0.9, 0.75, 0.2) + return label end function AltSystem:CreateMainFrame() - if AltSystem.MainFrame then return end + if AltSystem.MainFrame then + return + end -- Main frame local f = CreateFrame("Frame", "AltSystemMainFrame", UIParent, "BasicFrameTemplateWithInset") @@ -76,26 +139,142 @@ function AltSystem:CreateMainFrame() f.title:SetPoint("TOPLEFT", f.TitleBg, "TOPLEFT", 5, -3) f.title:SetText("AltSystem") - -- Track current Y offset for layout - local yPos = -40 + --------------------- + -- TAB BUTTONS (span full window width) + --------------------- + local contentTop = -24 + local tabHeight = 28 + local contentWidth = WINDOW_WIDTH - 8 -- 4px inset on each side + local tabWidth = contentWidth / 2 + + local tabUseSkills = CreateFrame("Button", "AltSystemTabUseSkills", f) + tabUseSkills:SetSize(tabWidth, tabHeight) + tabUseSkills:SetPoint("TOPLEFT", f, "TOPLEFT", 4, contentTop) + tabUseSkills:SetNormalFontObject("GameFontHighlight") + tabUseSkills:SetHighlightFontObject("GameFontHighlight") + tabUseSkills:SetText("Use Skills") + + local tabUseSkillsBg = tabUseSkills:CreateTexture(nil, "BACKGROUND") + tabUseSkillsBg:SetAllPoints() + tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + + local tabBuildSkills = CreateFrame("Button", "AltSystemTabBuildSkills", f) + tabBuildSkills:SetSize(tabWidth, tabHeight) + tabBuildSkills:SetPoint("TOPLEFT", tabUseSkills, "TOPRIGHT", 0, 0) + tabBuildSkills:SetNormalFontObject("GameFontHighlight") + tabBuildSkills:SetHighlightFontObject("GameFontHighlight") + tabBuildSkills:SetText("Build Skills") + + local tabBuildSkillsBg = tabBuildSkills:CreateTexture(nil, "BACKGROUND") + tabBuildSkillsBg:SetAllPoints() + tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + + --------------------- + -- TAB CONTENT FRAMES + --------------------- + local tabContentTop = contentTop - tabHeight + local tabContentHeight = WINDOW_HEIGHT - 28 - tabHeight + + local useSkillsContent = CreateFrame("Frame", "AltSystemUseSkillsContent", f) + useSkillsContent:SetPoint("TOPLEFT", f, "TOPLEFT", 4, tabContentTop) + useSkillsContent:SetSize(contentWidth, tabContentHeight) + + local buildSkillsContent = CreateFrame("Frame", "AltSystemBuildSkillsContent", f) + buildSkillsContent:SetPoint("TOPLEFT", f, "TOPLEFT", 4, tabContentTop) + buildSkillsContent:SetSize(contentWidth, tabContentHeight) + buildSkillsContent:Hide() + + local buildPlaceholder = buildSkillsContent:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + buildPlaceholder:SetPoint("CENTER") + buildPlaceholder:SetText("Coming soon") + + -- Tab switching logic + local function SelectTab(tabIndex) + if tabIndex == 1 then + useSkillsContent:Show() + buildSkillsContent:Hide() + tabUseSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + tabBuildSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + else + useSkillsContent:Hide() + buildSkillsContent:Show() + tabUseSkillsBg:SetColorTexture(0.3, 0.3, 0.3, 1) + tabBuildSkillsBg:SetColorTexture(0.15, 0.15, 0.15, 1) + end + end + + tabUseSkills:SetScript("OnClick", function() + SelectTab(1) + end) + tabBuildSkills:SetScript("OnClick", function() + SelectTab(2) + end) + + --------------------- + -- USE SKILLS TAB CONTENT (left: controls, right: log) + --------------------- + local controlsPanel = CreateFrame("Frame", nil, useSkillsContent) + controlsPanel:SetPoint("TOPLEFT", useSkillsContent, "TOPLEFT", 0, 0) + controlsPanel:SetSize(CONTROLS_WIDTH, tabContentHeight) + + local content = controlsPanel + local yPos = -PADDING + + -- Section: Define Your Base Roll + CreateSectionHeader(content, "Define Your Base Roll", PADDING, yPos) + yPos = yPos - 20 + + -- Roll Type label + CreateSubLabel(content, "Roll Type", PADDING, yPos) + yPos = yPos - 20 + + -- Roll Type radio buttons + local attackRadio, defenseRadio + + local function UpdateRollTypeSelection(rollType) + AltSystem.State.rollType = rollType + attackRadio:SetChecked(rollType == "attack") + defenseRadio:SetChecked(rollType == "defense") + -- Update roll button text + if AltSystem.RollButton then + local label = rollType == "attack" and "Roll Attack" or "Roll Defense" + AltSystem.RollButton:SetText(label) + end + end + + attackRadio = CreateRadioButton(content, "AltSystemAttackRadio", "Attack Roll", PADDING, yPos, + AltSystem.State.rollType == "attack", + function() + UpdateRollTypeSelection("attack") + end) + + defenseRadio = CreateRadioButton(content, "AltSystemDefenseRadio", "Defense Roll", CONTROLS_WIDTH / 2, yPos, + AltSystem.State.rollType == "defense", + function() + UpdateRollTypeSelection("defense") + end) + + yPos = yPos - ROW_HEIGHT - SECTION_GAP - ------------------------- -- Skill dropdown - ------------------------- local skillOptions = BuildSkillOptions() local UpdateSkillWarning -- forward declaration - local skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown( - f, "AltSystemSkillDropdown", yPos, "Skill:", skillOptions, - AltSystem.State.selectedSkillIndex, - function(index) - AltSystem.State.selectedSkillIndex = index - AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil - UpdateSkillWarning(index) - end) + local skillContainer, skillDropdown, getSkillIndex, setSkillIndex = CreateDropdown( + content, "AltSystemSkillDropdown", "Skill", skillOptions, + AltSystem.State.selectedSkillIndex, + function(index) + AltSystem.State.selectedSkillIndex = index + AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil + UpdateSkillWarning(index) + end, + "GameFontNormalSmall" + ) + skillContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + skillContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) - -- Warning icon for skill mismatch (shown next to dropdown when value doesn't match keyword) - local skillWarning = CreateFrame("Frame", nil, f) + -- Warning icon for skill mismatch + local skillWarning = CreateFrame("Frame", nil, skillContainer) skillWarning:SetSize(20, 20) skillWarning:SetPoint("LEFT", skillDropdown, "RIGHT", 4, 0) @@ -115,7 +294,6 @@ function AltSystem:CreateMainFrame() end) skillWarning:Hide() - -- Update the warning icon visibility based on the currently selected skill UpdateSkillWarning = function(index) local skill = AltSystem.Data.Skills[index] if skill and skill.warning then @@ -131,186 +309,212 @@ function AltSystem:CreateMainFrame() AltSystem.GetSkillIndex = getSkillIndex AltSystem.SetSkillIndex = setSkillIndex AltSystem.UpdateSkillWarning = UpdateSkillWarning - yPos = yPos - ROW_HEIGHT - 8 - ------------------------- - -- Item dropdown - ------------------------- - local itemOptions = {} - for _, item in ipairs(AltSystem.Data.Items) do - local sign = item.modifier >= 0 and "+" or "" - local modText = item.modifier ~= 0 and (" (" .. sign .. item.modifier .. ")") or "" - table.insert(itemOptions, { - text = item.name .. modText, - }) + yPos = yPos - ROW_HEIGHT - SECTION_GAP + + -- Armor label + CreateSubLabel(content, "Extra Armor", PADDING, yPos) + yPos = yPos - 20 + + -- Armor radio buttons + local armorRadios = {} + + local function UpdateArmorSelection(index) + AltSystem.State.selectedDefenseIndex = index + for i, radio in ipairs(armorRadios) do + radio:SetChecked(i == index) + end end - CreateDropdown(f, "AltSystemItemDropdown", yPos, "Item:", itemOptions, AltSystem.State.selectedItemIndex, - function(index) - AltSystem.State.selectedItemIndex = index - end) - yPos = yPos - ROW_HEIGHT - 8 - - ------------------------- - -- Defense dropdown - ------------------------- - local defenseOptions = {} - for _, def in ipairs(AltSystem.Data.Defenses) do - local sign = def.modifier >= 0 and "+" or "" - local modText = " (" .. sign .. def.modifier .. ")" - table.insert(defenseOptions, { - text = def.name .. modText, - }) + local armorX = PADDING + for i, def in ipairs(AltSystem.Data.Defenses) do + local text = def.name + if def.modifier > 0 then + text = text .. " (+" .. def.modifier .. ")" + end + local radio = CreateRadioButton(content, "AltSystemArmorRadio" .. i, text, armorX, yPos, + AltSystem.State.selectedDefenseIndex == i, + function() + UpdateArmorSelection(i) + end) + table.insert(armorRadios, radio) + armorX = armorX + 110 end - CreateDropdown(f, "AltSystemDefenseDropdown", yPos, "Defense:", defenseOptions, AltSystem.State.selectedDefenseIndex, - function(index) - AltSystem.State.selectedDefenseIndex = index - end) - yPos = yPos - ROW_HEIGHT - 8 + yPos = yPos - ROW_HEIGHT - SECTION_GAP + + -- Section: Modifiers (optional) + CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) + yPos = yPos - 18 + --CreateSubLabel(content, "Label", PADDING, yPos) + --yPos = yPos - 22 - ------------------------- -- Shield checkbox - ------------------------- - local shieldLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") - shieldLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - shieldLabel:SetText("Shield:") - shieldLabel:SetWidth(LABEL_WIDTH) - shieldLabel:SetJustifyH("LEFT") - - local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", f, "UICheckButtonTemplate") - shieldCheck:SetPoint("LEFT", shieldLabel, "RIGHT", -6, 0) + local shieldCheck = CreateFrame("CheckButton", "AltSystemShieldCheck", content, "UICheckButtonTemplate") + shieldCheck:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) shieldCheck:SetChecked(AltSystem.State.shieldEnabled) - local shieldText = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") - shieldText:SetPoint("LEFT", shieldCheck, "RIGHT", 2, 0) - shieldText:SetText("+1 modifier") + local shieldText = shieldCheck:GetFontString() + if shieldText then + shieldText:SetText("Shield (+ 1)") + else + shieldText = shieldCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + shieldText:SetPoint("LEFT", shieldCheck, "RIGHT", 2, 0) + shieldText:SetText("Shield (+ 1)") + end shieldCheck:SetScript("OnClick", function(self) AltSystem.State.shieldEnabled = self:GetChecked() end) - yPos = yPos - ROW_HEIGHT - 8 - - ------------------------- - -- Pet/Summon checkbox - ------------------------- - local petLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") - petLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - petLabel:SetText("Pet:") - petLabel:SetWidth(LABEL_WIDTH) - petLabel:SetJustifyH("LEFT") - - local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", f, "UICheckButtonTemplate") - petCheck:SetPoint("LEFT", petLabel, "RIGHT", -6, 0) + -- Pet checkbox + local petCheck = CreateFrame("CheckButton", "AltSystemPetSummonCheck", content, "UICheckButtonTemplate") + petCheck:SetPoint("LEFT", shieldCheck, "RIGHT", 80, 0) petCheck:SetChecked(AltSystem.State.petSummonEnabled) - local petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") - petText:SetPoint("LEFT", petCheck, "RIGHT", 2, 0) - petText:SetText("+d5 modifier") + local petText = petCheck:GetFontString() + if petText then + petText:SetText("Pet (+d5)") + else + petText = petCheck:CreateFontString(nil, "OVERLAY", "GameFontHighlight") + petText:SetPoint("LEFT", petCheck, "RIGHT", 2, 0) + petText:SetText("Pet (+d5)") + end petCheck:SetScript("OnClick", function(self) AltSystem.State.petSummonEnabled = self:GetChecked() end) - yPos = yPos - ROW_HEIGHT - 8 + yPos = yPos - ROW_HEIGHT - SECTION_GAP - ------------------------- - -- Announce checkbox + channel dropdown - ------------------------- - local announceLabel = f:CreateFontString(nil, "OVERLAY", "GameFontNormal") - announceLabel:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - announceLabel:SetText("Announce:") - announceLabel:SetWidth(LABEL_WIDTH) - announceLabel:SetJustifyH("LEFT") + -- Section: Roll Dice + CreateSectionHeader(content, "Roll Dice", PADDING, yPos) + yPos = yPos - 22 - local announceCheck = CreateFrame("CheckButton", "AltSystemAnnounceCheck", f, "UICheckButtonTemplate") - announceCheck:SetPoint("LEFT", announceLabel, "RIGHT", -6, 0) - announceCheck:SetChecked(AltSystem.State.announceEnabled) - - -- Channel dropdown (shown only when announce is enabled) - local channelOptions = {} + -- Announce Roll dropdown (Self Roll + channels) + local announceOptions = { { text = "Self Roll" } } for _, ch in ipairs(AltSystem.AnnounceChannels) do - table.insert(channelOptions, { text = ch.name }) + table.insert(announceOptions, { text = ch.name }) end - local channelDropdown, getChannelIndex, setChannelIndex = CreateDropdown( - f, "AltSystemChannelDropdown", yPos, "", channelOptions, - AltSystem.State.announceChannelIndex, - function(index) - AltSystem.State.announceChannelIndex = index - end) - channelDropdown:SetPoint("LEFT", announceCheck, "RIGHT", 2, 0) - channelDropdown:SetWidth(130) + local announceContainer, announceDropdown = CreateDropdown( + content, "AltSystemAnnounceDropdown", "Announce Roll", announceOptions, + AltSystem.State.announceOptionIndex, + function(index) + AltSystem.State.announceOptionIndex = index + if index > 1 then + AltSystem.State.announceEnabled = true + AltSystem.State.announceChannelIndex = index - 1 + else + AltSystem.State.announceEnabled = false + AltSystem.State.announceChannelIndex = 1 + end + end, + "GameFontNormalSmall") + announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) - -- Show/hide channel dropdown based on checkbox state - local function UpdateChannelDropdownVisibility() - if AltSystem.State.announceEnabled then - channelDropdown:Show() - else - channelDropdown:Hide() - end - end + yPos = yPos - ROW_HEIGHT - SECTION_GAP - announceCheck:SetScript("OnClick", function(self) - AltSystem.State.announceEnabled = self:GetChecked() - UpdateChannelDropdownVisibility() + -- Roll button + local rollBtn = CreateFrame("Button", "AltSystemRollBtn", content, "UIPanelButtonTemplate") + rollBtn:SetSize(CONTROLS_WIDTH - PADDING * 2, 32) + rollBtn:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) + local rollLabel = AltSystem.State.rollType == "attack" and "Roll Attack" or "Roll Defense" + rollBtn:SetText(rollLabel) + + rollBtn:SetScript("OnClick", function() + AltSystem:PerformRoll(AltSystem.State.rollType) end) - UpdateChannelDropdownVisibility() + AltSystem.RollButton = rollBtn - yPos = yPos - ROW_HEIGHT - 12 + --------------------- + -- LOG PANEL (right side of Use Skills tab) + --------------------- + local logPanel = CreateFrame("Frame", nil, useSkillsContent) + logPanel:SetPoint("TOPLEFT", useSkillsContent, "TOPLEFT", CONTROLS_WIDTH, 0) + logPanel:SetSize(LOG_WIDTH, tabContentHeight) - ------------------------- - -- Roll buttons - ------------------------- - local btnWidth = (WINDOW_WIDTH - PADDING * 3) / 2 + -- Log header + local logHeader = logPanel:CreateFontString(nil, "OVERLAY", "GameFontNormal") + logHeader:SetPoint("TOPLEFT", logPanel, "TOPLEFT", PADDING, -4) + logHeader:SetText("Log") + logHeader:SetTextColor(0.9, 0.75, 0.2) - local attackBtn = CreateFrame("Button", "AltSystemAttackRollBtn", f, "UIPanelButtonTemplate") - attackBtn:SetSize(btnWidth, 28) - attackBtn:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - attackBtn:SetText("Attack/Skill Roll") + -- Log scroll area background + local logBg = CreateFrame("Frame", nil, logPanel, "InsetFrameTemplate") + logBg:SetPoint("TOPLEFT", logPanel, "TOPLEFT", 4, -22) + logBg:SetPoint("BOTTOMRIGHT", logPanel, "BOTTOMRIGHT", -4, 4) - local defenseBtn = CreateFrame("Button", "AltSystemDefenseRollBtn", f, "UIPanelButtonTemplate") - defenseBtn:SetSize(btnWidth, 28) - defenseBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -PADDING, yPos) - defenseBtn:SetText("Defense Roll") + -- Scroll frame for log entries + local scrollFrame = CreateFrame("ScrollFrame", "AltSystemLogScrollFrame", logBg, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", logBg, "TOPLEFT", 6, -6) + scrollFrame:SetPoint("BOTTOMRIGHT", logBg, "BOTTOMRIGHT", -28, 6) - yPos = yPos - 40 + local scrollChild = CreateFrame("Frame", "AltSystemLogScrollChild", scrollFrame) + scrollChild:SetWidth(scrollFrame:GetWidth() or (LOG_WIDTH - 50)) + scrollChild:SetHeight(1) -- Will be updated dynamically + scrollFrame:SetScrollChild(scrollChild) - ------------------------- - -- Roll result area - ------------------------- - local resultBg = CreateFrame("Frame", nil, f, "InsetFrameTemplate") - resultBg:SetPoint("TOPLEFT", f, "TOPLEFT", PADDING, yPos) - resultBg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -PADDING, PADDING) - - local resultText = resultBg:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") - resultText:SetPoint("CENTER") - resultText:SetText("Roll result will appear here") - resultText:SetJustifyH("CENTER") - resultText:SetWidth(resultBg:GetWidth() - 10) - - AltSystem.ResultText = resultText - - -- Button click handlers - attackBtn:SetScript("OnClick", function() - AltSystem:PerformRoll("attack") - end) - - defenseBtn:SetScript("OnClick", function() - AltSystem:PerformRoll("defense") - end) + AltSystem.LogScrollFrame = scrollFrame + AltSystem.LogScrollChild = scrollChild -- Refresh skills from TRP3 profile each time the window is shown f:SetScript("OnShow", function() AltSystem:RefreshSkillDropdown() + AltSystem:RefreshLogPanel() end) f:Hide() AltSystem.MainFrame = f end +-- Refresh the log panel UI from AltSystem.State.rollLog +function AltSystem:RefreshLogPanel() + local scrollChild = AltSystem.LogScrollChild + if not scrollChild then + return + end + + -- Remove existing log entry fontstrings + if scrollChild.entries then + for _, entry in ipairs(scrollChild.entries) do + entry:Hide() + entry:SetText("") + end + end + scrollChild.entries = scrollChild.entries or {} + + local rollLog = AltSystem.State.rollLog + local entryHeight = 16 + local spacing = 4 + local yPos = 0 + local childWidth = AltSystem.LogScrollFrame:GetWidth() or 250 + + -- Entries are newest-first + for i = #rollLog, 1, -1 do + local idx = #rollLog - i + 1 + local logEntry = rollLog[i] + local fontStr = scrollChild.entries[idx] + + if not fontStr then + fontStr = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") + scrollChild.entries[idx] = fontStr + end + + fontStr:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 2, -yPos) + fontStr:SetWidth(childWidth - 4) + fontStr:SetJustifyH("LEFT") + fontStr:SetText(logEntry.text) + fontStr:Show() + + yPos = yPos + entryHeight + spacing + end + + scrollChild:SetHeight(math.max(yPos, 1)) +end + -- Refresh the skill dropdown with current TRP3 profile data function AltSystem:RefreshSkillDropdown() AltSystem.Data:RefreshSkills() @@ -341,19 +545,21 @@ function AltSystem:RefreshSkillDropdown() AltSystem.SkillDropdown:SetupMenu(function(dropdown, rootDescription) for i, option in ipairs(skillOptions) do rootDescription:CreateRadio( - option.text, - function(data) return data == AltSystem.State.selectedSkillIndex end, - function(data) - AltSystem.State.selectedSkillIndex = data - AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil - if AltSystem.SetSkillIndex then - AltSystem.SetSkillIndex(data) - end - if AltSystem.UpdateSkillWarning then - AltSystem.UpdateSkillWarning(data) - end - end, - i + option.text, + function(data) + return data == AltSystem.State.selectedSkillIndex + end, + function(data) + AltSystem.State.selectedSkillIndex = data + AltSystem.State.selectedSkillName = AltSystem.Data.Skills[data] and AltSystem.Data.Skills[data].name or nil + if AltSystem.SetSkillIndex then + AltSystem.SetSkillIndex(data) + end + if AltSystem.UpdateSkillWarning then + AltSystem.UpdateSkillWarning(data) + end + end, + i ) end end) From 0680020808db44d9c4b1156eb601b1e5d9df96e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 16:31:54 +0100 Subject: [PATCH 4/5] Minor adjustments --- UI.lua | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/UI.lua b/UI.lua index fdbdace..4412ec0 100644 --- a/UI.lua +++ b/UI.lua @@ -5,10 +5,10 @@ AltSystem = AltSystem or {} -local WINDOW_WIDTH = 726 +local WINDOW_WIDTH = 700 local WINDOW_HEIGHT = 500 -local CONTROLS_WIDTH = 363 -local LOG_WIDTH = 363 +local CONTROLS_WIDTH = 350 +local LOG_WIDTH = 350 local PADDING = 12 local ROW_HEIGHT = 26 local SECTION_GAP = 10 @@ -46,7 +46,7 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on local dropdown = CreateFrame("DropdownButton", name, container, "WowStyle1DropdownTemplate") if label then - dropdown:SetPoint("LEFT", label, "RIGHT", 8, 0) + dropdown:SetPoint("RIGHT", container, "RIGHT", 0, 0) else dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) end @@ -111,7 +111,7 @@ end -- Helper: Create a sub-label (smaller golden text) local function CreateSubLabel(parent, text, x, y) - local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + local label = parent:CreateFontString(nil, "OVERLAY", "GameFontNormal") label:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y) label:SetText(text) label:SetTextColor(0.9, 0.75, 0.2) @@ -268,7 +268,7 @@ function AltSystem:CreateMainFrame() AltSystem.State.selectedSkillName = AltSystem.Data.Skills[index] and AltSystem.Data.Skills[index].name or nil UpdateSkillWarning(index) end, - "GameFontNormalSmall" + "GameFontNormal" ) skillContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) skillContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) @@ -410,7 +410,7 @@ function AltSystem:CreateMainFrame() AltSystem.State.announceChannelIndex = 1 end end, - "GameFontNormalSmall") + "GameFontNormal") announceContainer:SetPoint("TOPLEFT", content, "TOPLEFT", PADDING, yPos) announceContainer:SetWidth(CONTROLS_WIDTH - PADDING * 2) From 5089a1dea45c9f9f252fc4a6c3ec0e2d01126f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Correia?= Date: Tue, 12 May 2026 16:36:26 +0100 Subject: [PATCH 5/5] Messing with sizes and padding --- UI.lua | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/UI.lua b/UI.lua index 4412ec0..0b989bb 100644 --- a/UI.lua +++ b/UI.lua @@ -50,7 +50,7 @@ local function CreateDropdown(parent, name, labelText, options, defaultIndex, on else dropdown:SetPoint("LEFT", container, "LEFT", 0, 0) end - dropdown:SetWidth(180) + dropdown:SetWidth(190) dropdown:SetupMenu(function(dropdown, rootDescription) for i, option in ipairs(options) do @@ -343,6 +343,37 @@ function AltSystem:CreateMainFrame() yPos = yPos - ROW_HEIGHT - SECTION_GAP + -- Item label + CreateSubLabel(content, "Item", PADDING, yPos) + yPos = yPos - 20 + + -- Item radio buttons + local itemRadios = {} + + local function UpdateItemSelection(index) + AltSystem.State.selectedItemIndex = index + for i, radio in ipairs(itemRadios) do + radio:SetChecked(i == index) + end + end + + local itemX = PADDING + for i, item in ipairs(AltSystem.Data.Items) do + local text = item.name + if item.modifier > 0 then + text = text .. " (+" .. item.modifier .. ")" + end + local radio = CreateRadioButton(content, "AltSystemItemRadio" .. i, text, itemX, yPos, + AltSystem.State.selectedItemIndex == i, + function() + UpdateItemSelection(i) + end) + table.insert(itemRadios, radio) + itemX = itemX + 110 + end + + yPos = yPos - ROW_HEIGHT - SECTION_GAP + -- Section: Modifiers (optional) CreateSectionHeader(content, "Modifiers (optional)", PADDING, yPos) yPos = yPos - 18