From 94de696af1f53549b310033228284dab572a6053 Mon Sep 17 00:00:00 2001 From: hz Date: Thu, 30 Jul 2026 05:18:39 -0500 Subject: [PATCH] Fix shield icon rendering, move slot machine to a movable panel The shield icon's dark/mushy rendering was caused by disabled mipmaps on all 6 slot-machine icon textures (inherited from the flat lucide icon convention, wrong for these gradient-heavy assets) - at small display sizes this caused aliasing that hit the shield's fine detail (RZ lettering, metallic bevels) hardest since the other icons are bolder/simpler shapes. Enabling mipmaps/generate on all 6 fixes it; confirmed by rendering the actual in-game presentation size before and after, not just eyeballing the source file. Also converts the slot machine from a full standalone screen (sidebar nav entry, main.gd-managed) into a movable/draggable floating panel opened from the question-player toolbar, mirroring the existing calculator panel exactly: same top_level+custom title-bar-drag+ viewport-clamp mechanics, same open/close/toggle API, instantiated once alongside the calculator and closed on session hide. Shrunk the cabinet and restacked the rewards list below it (was beside it) to fit a toolbox-sized floating panel instead of a full page, wrapped in a ScrollContainer for any overflow. Removed the sidebar entry and screens[] registration from main.gd and the now-dead routing case from navigation_routes.gd. --- assets/slot_machine/cabinet_frame.png.import | 2 +- assets/slot_machine/lever.png.import | 2 +- assets/slot_machine/slot_coin.png.import | 2 +- .../slot_machine/slot_coin_stack.png.import | 2 +- assets/slot_machine/slot_gem.png.import | 2 +- assets/slot_machine/slot_gift.png.import | 2 +- assets/slot_machine/slot_potion.png.import | 2 +- assets/slot_machine/slot_shield.png.import | 2 +- scripts/ui/main.gd | 7 - scripts/ui/navigation_routes.gd | 2 - scripts/ui/question_player.gd | 38 ++- scripts/ui/rz_slot_machine_screen.gd | 299 ++++++++++++------ 12 files changed, 247 insertions(+), 115 deletions(-) diff --git a/assets/slot_machine/cabinet_frame.png.import b/assets/slot_machine/cabinet_frame.png.import index e8ef167..0d0af7d 100644 --- a/assets/slot_machine/cabinet_frame.png.import +++ b/assets/slot_machine/cabinet_frame.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/lever.png.import b/assets/slot_machine/lever.png.import index 46e4b11..03b3f53 100644 --- a/assets/slot_machine/lever.png.import +++ b/assets/slot_machine/lever.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/slot_coin.png.import b/assets/slot_machine/slot_coin.png.import index 8e6b59b..5889b45 100644 --- a/assets/slot_machine/slot_coin.png.import +++ b/assets/slot_machine/slot_coin.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/slot_coin_stack.png.import b/assets/slot_machine/slot_coin_stack.png.import index 2e4191e..e8bd189 100644 --- a/assets/slot_machine/slot_coin_stack.png.import +++ b/assets/slot_machine/slot_coin_stack.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/slot_gem.png.import b/assets/slot_machine/slot_gem.png.import index 2131846..c46af92 100644 --- a/assets/slot_machine/slot_gem.png.import +++ b/assets/slot_machine/slot_gem.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/slot_gift.png.import b/assets/slot_machine/slot_gift.png.import index f051bb5..70ac06e 100644 --- a/assets/slot_machine/slot_gift.png.import +++ b/assets/slot_machine/slot_gift.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/slot_potion.png.import b/assets/slot_machine/slot_potion.png.import index 2349826..5dfb163 100644 --- a/assets/slot_machine/slot_potion.png.import +++ b/assets/slot_machine/slot_potion.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/assets/slot_machine/slot_shield.png.import b/assets/slot_machine/slot_shield.png.import index 06cd9fa..5d0c47d 100644 --- a/assets/slot_machine/slot_shield.png.import +++ b/assets/slot_machine/slot_shield.png.import @@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0 compress/hdr_compression=1 compress/normal_map=0 compress/channel_pack=0 -mipmaps/generate=false +mipmaps/generate=true mipmaps/limit=-1 roughness/mode=0 roughness/src_normal="" diff --git a/scripts/ui/main.gd b/scripts/ui/main.gd index d7a1475..35ef020 100644 --- a/scripts/ui/main.gd +++ b/scripts/ui/main.gd @@ -361,11 +361,6 @@ func _build_ui() -> void: rz_store.main = self screens_container.add_child(rz_store) screens["rz_store"] = rz_store - var slot_machine := preload("res://scripts/ui/rz_slot_machine_screen.gd").new() - slot_machine.name = "RzSlotMachine" - slot_machine.main = self - screens_container.add_child(slot_machine) - screens["slot_machine"] = slot_machine feedback_dialog = preload("res://scripts/ui/app_feedback_dialog.gd").new() feedback_dialog.name = "AppFeedbackDialog" @@ -445,7 +440,6 @@ func _populate_navigation(container: VBoxContainer, include_brand: bool) -> void _add_nav_button(container, "donations", "Support RanaZe", "heart-pulse", true) _add_nav_button(container, "performance", "Profile", "user-round", true) _add_nav_button(container, "rz_store", "RZ Shop", "star", true) - _add_nav_button(container, "slot_machine", "Slot Machine", "gem", true) _add_nav_button(container, "settings", "Settings", "settings", true) _add_nav_button(container, "logout", "Log out", "log-out", true) @@ -490,7 +484,6 @@ func _on_navigation_pressed(key: String) -> void: "bookmarks": show_screen("bookmarks") "donations": show_screen("donations") "rz_store": show_screen("rz_store") - "slot_machine": show_screen("slot_machine") "logout": _logout() diff --git a/scripts/ui/navigation_routes.gd b/scripts/ui/navigation_routes.gd index 23d0a6e..c9a1005 100644 --- a/scripts/ui/navigation_routes.gd +++ b/scripts/ui/navigation_routes.gd @@ -31,6 +31,4 @@ static func key_for_screen(screen_name: String, payload: Variant = null) -> Stri key = "donations" elif screen_name == "rz_store": key = "rz_store" - elif screen_name == "slot_machine": - key = "slot_machine" return key diff --git a/scripts/ui/question_player.gd b/scripts/ui/question_player.gd index 275b1ec..b8aa4a6 100644 --- a/scripts/ui/question_player.gd +++ b/scripts/ui/question_player.gd @@ -4,6 +4,7 @@ const Style := preload("res://scripts/ui/ui_style.gd") const Brand := preload("res://scripts/ui/medmind_style.gd") const FlashcardPrefillDialog := preload("res://scripts/ui/flashcard_prefill_dialog.gd") const CalculatorPanel := preload("res://scripts/ui/calculator_panel.gd") +const SlotMachinePanel := preload("res://scripts/ui/rz_slot_machine_screen.gd") const CreditsBadge := preload("res://scripts/ui/credits_badge.gd") const CLEAR_HIGHLIGHT_DARK_ICON := preload("res://assets/icons/medmind/clear-highlight-dark.svg") const CLEAR_HIGHLIGHT_LIGHT_ICON := preload("res://assets/icons/medmind/clear-highlight-light.svg") @@ -94,6 +95,7 @@ var text_smaller_button: Button var text_larger_button: Button var labs_button: Button var calculator_button: Button +var slot_machine_button: Button var exit_button: Button var clear_highlight_button: Button var flag_button: Button @@ -127,6 +129,7 @@ var labs_overlay: Control var labs_panel: PanelContainer var labs_content: VBoxContainer var calculator_panel +var slot_machine_panel var report_overlay: Control var report_panel: PanelContainer var report_form_content: VBoxContainer @@ -347,6 +350,18 @@ func _ready() -> void: UISoundService.set_button_sound(calculator_button, "open") calculator_button.pressed.connect(_toggle_calculator) + slot_machine_button = Button.new() + slot_machine_button.name = "SlotMachineButton" + slot_machine_button.tooltip_text = "Open RZ Slot Machine" + slot_machine_button.icon = preload("res://assets/icons/lucide/gem.svg") + slot_machine_button.custom_minimum_size = Vector2(44, 44) + slot_machine_button.add_theme_constant_override("icon_max_width", 22) + slot_machine_button.alignment = HORIZONTAL_ALIGNMENT_CENTER + slot_machine_button.icon_alignment = HORIZONTAL_ALIGNMENT_CENTER + slot_machine_button.focus_mode = Control.FOCUS_NONE + UISoundService.set_button_sound(slot_machine_button, "open") + slot_machine_button.pressed.connect(_toggle_slot_machine) + labs_button = Button.new() labs_button.tooltip_text = "Review lab results" labs_button.custom_minimum_size = Vector2(44, 44) @@ -432,6 +447,7 @@ func _ready() -> void: question_toolbar.add_child(text_smaller_button) question_toolbar.add_child(text_larger_button) question_toolbar.add_child(calculator_button) + question_toolbar.add_child(slot_machine_button) question_toolbar.add_child(labs_button) question_toolbar.add_child(clear_highlight_button) question_toolbar.add_child(flag_button) @@ -725,6 +741,9 @@ func _ready() -> void: _build_labs_modal() calculator_panel = CalculatorPanel.new() add_child(calculator_panel) + slot_machine_panel = SlotMachinePanel.new() + slot_machine_panel.main = main + add_child(slot_machine_panel) _build_report_modal() _build_media_viewer() resized.connect(_fit_modal_panels) @@ -810,10 +829,10 @@ func _refresh_responsive_layout() -> void: header_toolbar_host.reparent(target_parent) header_toolbar_host.size_flags_horizontal = Control.SIZE_EXPAND_FILL if compact else Control.SIZE_SHRINK_END question_toolbar.size_flags_horizontal = Control.SIZE_EXPAND_FILL - # 12 bare 44px icon buttons + the wider 92px Feedback button + the ~194px - # rank/ELO + points badge, with 8px gaps between all 14 - anything less + # 13 bare 44px icon buttons + the wider 92px Feedback button + the ~194px + # rank/ELO + points badge, with 8px gaps between all 15 - anything less # than this wraps onto a second row. - header_toolbar_host.custom_minimum_size.x = 0.0 if compact else 960.0 + header_toolbar_host.custom_minimum_size.x = 0.0 if compact else 1020.0 if size.x < 560.0: nav_palette.columns = 5 else: @@ -1069,7 +1088,7 @@ func _apply_question_theme() -> void: var navigator_heading := navigator_popup.find_child("NavigatorHeading", true, false) as Label if navigator_heading != null: navigator_heading.add_theme_color_override("font_color", Brand.text_color(dark)) - for control in [exit_button, text_smaller_button, text_larger_button, calculator_button, bookmark_button, notes_button, flashcard_button, app_feedback_button]: + for control in [exit_button, text_smaller_button, text_larger_button, calculator_button, slot_machine_button, bookmark_button, notes_button, flashcard_button, app_feedback_button]: if control == null: continue var active: bool = control == bookmark_button and BookmarkService.is_bookmarked(str(BlockSessionService.get_current_question().get("id", ""))) @@ -1085,6 +1104,8 @@ func _apply_question_theme() -> void: _style_theme_toggle_button(dark) if calculator_panel != null: calculator_panel.refresh_theme_controls() + if slot_machine_panel != null: + slot_machine_panel.refresh_theme_controls() if credits_badge != null: credits_badge.refresh_theme_controls() if skip_button != null: @@ -1135,6 +1156,8 @@ func on_hide() -> void: if calculator_panel != null: calculator_panel.close_panel() calculator_panel.reset_calculation() + if slot_machine_panel != null: + slot_machine_panel.close_panel() func _save_simulation_current_response() -> void: @@ -2365,6 +2388,8 @@ func _refresh_toolbar_state() -> void: bookmark_button.tooltip_text = ("Saved; waiting to sync" if bookmark_pending else "Remove bookmark") if bookmark_value else ("Removal waiting to sync" if bookmark_pending else "Save question") if calculator_button != null: calculator_button.visible = true + if slot_machine_button != null: + slot_machine_button.visible = true _apply_question_theme() @@ -2373,6 +2398,11 @@ func _toggle_calculator() -> void: calculator_panel.toggle_panel() +func _toggle_slot_machine() -> void: + if slot_machine_panel != null: + slot_machine_panel.toggle_panel() + + func _toggle_bookmark_current_question() -> void: var question := BlockSessionService.get_current_question() if not question.is_empty(): diff --git a/scripts/ui/rz_slot_machine_screen.gd b/scripts/ui/rz_slot_machine_screen.gd index d485609..ea99543 100644 --- a/scripts/ui/rz_slot_machine_screen.gd +++ b/scripts/ui/rz_slot_machine_screen.gd @@ -1,4 +1,4 @@ -extends VBoxContainer +extends PanelContainer const Brand := preload("res://scripts/ui/medmind_style.gd") const STAR_ICON := preload("res://assets/icons/lucide/star.svg") @@ -23,9 +23,15 @@ const REWARD_ICONS := { "nice": {"texture": SLOT_COIN_STACK, "modulate": false}, "good_luck": {"texture": SLOT_COIN, "modulate": false, "tint": true}, } +const MACHINE_SIZE := 380.0 +const CELL := 44.0 var main: Control +var drag_active := false +var drag_offset := Vector2.ZERO +var has_been_positioned := false + var subtitle_label: Label var machine_root: Control var lever_button: TextureButton @@ -49,67 +55,129 @@ var _spinning := false func _ready() -> void: - size_flags_horizontal = Control.SIZE_EXPAND_FILL - size_flags_vertical = Control.SIZE_EXPAND_FILL - add_theme_constant_override("separation", 18) + name = "RzSlotMachinePanel" + visible = false + top_level = true + mouse_filter = Control.MOUSE_FILTER_STOP + custom_minimum_size = Vector2(460, 680) + size = custom_minimum_size - _build_header() + _build_ui() - var body := HBoxContainer.new() - body.add_theme_constant_override("separation", 18) - body.size_flags_vertical = Control.SIZE_EXPAND_FILL - add_child(body) - - var left_column := VBoxContainer.new() - left_column.add_theme_constant_override("separation", 16) - left_column.size_flags_horizontal = Control.SIZE_EXPAND_FILL - body.add_child(left_column) - - _build_machine(left_column) - _build_controls(left_column) - _build_rewards_panel(body) - - status_label = Label.new() - status_label.add_theme_font_override("font", Brand.font) - status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - add_child(status_label) - - _build_footer() + if not get_viewport().size_changed.is_connected(_on_viewport_size_changed): + get_viewport().size_changed.connect(_on_viewport_size_changed) + _on_viewport_size_changed() CreditsService.credits_changed.connect(_refresh_spin_button) CreditsService.spin_config_loaded.connect(_refresh_rewards) - UIThemeService.theme_changed.connect(func(_dark: bool): _refresh_theme_controls()) + UIThemeService.theme_changed.connect(func(_dark: bool): refresh_theme_controls()) + refresh_theme_controls() _refresh_rewards() _refresh_spin_button() -func on_show(_payload: Variant = null) -> void: +func open_panel() -> void: + visible = true + move_to_front() + if not has_been_positioned: + var viewport_size := get_viewport_rect().size + position = Vector2(maxf(12.0, viewport_size.x - size.x - 24.0), 72.0) + has_been_positioned = true + _clamp_to_viewport() CreditsService.refresh() CreditsService.fetch_spin_config() _refresh_rewards() _refresh_spin_button() -func _build_header() -> void: - var title := Label.new() - title.text = "RZ Coin Slot Machine" - title.add_theme_font_override("font", Brand.font) - title.add_theme_font_size_override("font_size", 30) - add_child(title) +func close_panel() -> void: + visible = false + drag_active = false + + +func toggle_panel() -> void: + if visible: + close_panel() + else: + open_panel() + + +func _build_ui() -> void: + var margin := MarginContainer.new() + margin.add_theme_constant_override("margin_left", 12) + margin.add_theme_constant_override("margin_right", 12) + margin.add_theme_constant_override("margin_top", 12) + margin.add_theme_constant_override("margin_bottom", 14) + add_child(margin) + + var content := VBoxContainer.new() + content.add_theme_constant_override("separation", 10) + margin.add_child(content) + + _build_title_bar(content) + + var scroll := ScrollContainer.new() + scroll.size_flags_vertical = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + content.add_child(scroll) + + var scroll_content := VBoxContainer.new() + scroll_content.add_theme_constant_override("separation", 14) + scroll_content.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll.add_child(scroll_content) + subtitle_label = Label.new() subtitle_label.text = "Test your luck and win exciting rewards!" subtitle_label.add_theme_font_override("font", Brand.font) - subtitle_label.add_theme_font_size_override("font_size", 14) - subtitle_label.add_theme_color_override("font_color", Brand.muted_color(UIThemeService.is_dark_mode())) - add_child(subtitle_label) + subtitle_label.add_theme_font_size_override("font_size", 13) + subtitle_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + scroll_content.add_child(subtitle_label) + + _build_machine(scroll_content) + _build_controls(scroll_content) + _build_rewards_section(scroll_content) + _build_footer(scroll_content) + + status_label = Label.new() + status_label.add_theme_font_override("font", Brand.font) + status_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + status_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + scroll_content.add_child(status_label) + + +func _build_title_bar(content: VBoxContainer) -> void: + var title_bar := HBoxContainer.new() + title_bar.name = "SlotMachineTitleBar" + title_bar.mouse_filter = Control.MOUSE_FILTER_STOP + title_bar.custom_minimum_size.y = 34 + title_bar.gui_input.connect(_on_title_bar_gui_input) + content.add_child(title_bar) + + var title := Label.new() + title.text = "RZ Slot Machine" + title.size_flags_horizontal = Control.SIZE_EXPAND_FILL + title.mouse_filter = Control.MOUSE_FILTER_IGNORE + title.add_theme_font_override("font", Brand.font) + title.add_theme_font_size_override("font_size", 18) + title_bar.add_child(title) + + var close := Button.new() + close.name = "SlotMachineClose" + close.text = "×" + close.tooltip_text = "Close slot machine" + close.custom_minimum_size = Vector2(36, 32) + close.focus_mode = Control.FOCUS_NONE + close.pressed.connect(close_panel) + title_bar.add_child(close) func _build_machine(parent: Control) -> void: + var center := CenterContainer.new() + parent.add_child(center) + machine_root = Control.new() - machine_root.custom_minimum_size = Vector2(560, 560) - machine_root.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN - machine_root.size_flags_vertical = Control.SIZE_SHRINK_BEGIN - parent.add_child(machine_root) + machine_root.custom_minimum_size = Vector2(MACHINE_SIZE, MACHINE_SIZE) + center.add_child(machine_root) var frame := TextureRect.new() frame.texture = CABINET_FRAME @@ -127,20 +195,19 @@ func _build_machine(parent: Control) -> void: window.mouse_filter = Control.MOUSE_FILTER_IGNORE machine_root.add_child(window) - var center := CenterContainer.new() - center.set_anchors_preset(Control.PRESET_FULL_RECT) - window.add_child(center) + var reel_center := CenterContainer.new() + reel_center.set_anchors_preset(Control.PRESET_FULL_RECT) + window.add_child(reel_center) var reels_grid := HBoxContainer.new() - reels_grid.add_theme_constant_override("separation", 16) - center.add_child(reels_grid) + reels_grid.add_theme_constant_override("separation", 10) + reel_center.add_child(reels_grid) - var cell := 66.0 reel_states.clear() for _column_index in range(3): var clip := Control.new() clip.clip_contents = true - clip.custom_minimum_size = Vector2(cell, cell * 3) + clip.custom_minimum_size = Vector2(CELL, CELL * 3) reels_grid.add_child(clip) var strip := VBoxContainer.new() @@ -151,7 +218,7 @@ func _build_machine(parent: Control) -> void: for _row_index in range(4): var icon := TextureRect.new() icon.texture = REEL_SYMBOLS[randi() % REEL_SYMBOLS.size()] - icon.custom_minimum_size = Vector2(cell, cell) + icon.custom_minimum_size = Vector2(CELL, CELL) icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED strip.add_child(icon) @@ -160,8 +227,8 @@ func _build_machine(parent: Control) -> void: reel_states.append({ "strip": strip, "icons": icons, - "step": cell, - "speed": 620.0, + "step": CELL, + "speed": 420.0, "spinning": false, "stop_requested": false, "target_texture": null, @@ -199,22 +266,22 @@ func _build_controls(parent: Control) -> void: win_up_to_label = Label.new() win_up_to_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER win_up_to_label.add_theme_font_override("font", Brand.font) - win_up_to_label.add_theme_font_size_override("font_size", 15) + win_up_to_label.add_theme_font_size_override("font_size", 14) win_up_to_label.add_theme_color_override("font_color", Brand.text_color(dark)) win_margin.add_child(win_up_to_label) var controls_row := HBoxContainer.new() - controls_row.add_theme_constant_override("separation", 12) + controls_row.add_theme_constant_override("separation", 10) parent.add_child(controls_row) cost_card = PanelContainer.new() cost_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) controls_row.add_child(cost_card) var cost_margin := MarginContainer.new() - cost_margin.add_theme_constant_override("margin_left", 14) - cost_margin.add_theme_constant_override("margin_right", 14) - cost_margin.add_theme_constant_override("margin_top", 8) - cost_margin.add_theme_constant_override("margin_bottom", 8) + cost_margin.add_theme_constant_override("margin_left", 10) + cost_margin.add_theme_constant_override("margin_right", 10) + cost_margin.add_theme_constant_override("margin_top", 6) + cost_margin.add_theme_constant_override("margin_bottom", 6) cost_card.add_child(cost_margin) var cost_box := VBoxContainer.new() cost_box.add_theme_constant_override("separation", 0) @@ -222,22 +289,22 @@ func _build_controls(parent: Control) -> void: var cost_caption := Label.new() cost_caption.text = "Cost per spin" cost_caption.add_theme_font_override("font", Brand.font) - cost_caption.add_theme_font_size_override("font_size", 11) + cost_caption.add_theme_font_size_override("font_size", 10) cost_caption.add_theme_color_override("font_color", Brand.muted_color(dark)) cost_box.add_child(cost_caption) cost_value_label = Label.new() cost_value_label.add_theme_font_override("font", Brand.font) - cost_value_label.add_theme_font_size_override("font_size", 15) + cost_value_label.add_theme_font_size_override("font_size", 14) cost_value_label.add_theme_color_override("font_color", Brand.text_color(dark)) cost_box.add_child(cost_value_label) spin_button = Button.new() spin_button.text = "Spin" - spin_button.custom_minimum_size = Vector2(140, 58) + spin_button.custom_minimum_size = Vector2(110, 52) spin_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL spin_button.size_flags_vertical = Control.SIZE_SHRINK_CENTER spin_button.add_theme_font_override("font", Brand.font) - spin_button.add_theme_font_size_override("font_size", 17) + spin_button.add_theme_font_size_override("font_size", 16) _style_spin_button() spin_button.pressed.connect(_on_spin_pressed) controls_row.add_child(spin_button) @@ -246,10 +313,10 @@ func _build_controls(parent: Control) -> void: auto_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) controls_row.add_child(auto_card) var auto_margin := MarginContainer.new() - auto_margin.add_theme_constant_override("margin_left", 14) - auto_margin.add_theme_constant_override("margin_right", 14) - auto_margin.add_theme_constant_override("margin_top", 8) - auto_margin.add_theme_constant_override("margin_bottom", 8) + auto_margin.add_theme_constant_override("margin_left", 10) + auto_margin.add_theme_constant_override("margin_right", 10) + auto_margin.add_theme_constant_override("margin_top", 6) + auto_margin.add_theme_constant_override("margin_bottom", 6) auto_card.add_child(auto_margin) var auto_box := VBoxContainer.new() auto_box.add_theme_constant_override("separation", 0) @@ -261,31 +328,30 @@ func _build_controls(parent: Control) -> void: var auto_caption := Label.new() auto_caption.text = "10 spins" auto_caption.add_theme_font_override("font", Brand.font) - auto_caption.add_theme_font_size_override("font_size", 11) + auto_caption.add_theme_font_size_override("font_size", 10) auto_caption.add_theme_color_override("font_color", Brand.muted_color(dark)) auto_box.add_child(auto_caption) var info_label := Label.new() info_label.text = "All rewards are instant and sent to your account." info_label.add_theme_font_override("font", Brand.font) - info_label.add_theme_font_size_override("font_size", 11) + info_label.add_theme_font_size_override("font_size", 10) info_label.add_theme_color_override("font_color", Brand.muted_color(dark)) info_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER parent.add_child(info_label) -func _build_rewards_panel(parent: Control) -> void: +func _build_rewards_section(parent: Control) -> void: var dark := UIThemeService.is_dark_mode() var panel := PanelContainer.new() - panel.custom_minimum_size = Vector2(280, 0) panel.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) parent.add_child(panel) var margin := MarginContainer.new() - margin.add_theme_constant_override("margin_left", 16) - margin.add_theme_constant_override("margin_right", 16) - margin.add_theme_constant_override("margin_top", 14) - margin.add_theme_constant_override("margin_bottom", 14) + margin.add_theme_constant_override("margin_left", 14) + margin.add_theme_constant_override("margin_right", 14) + margin.add_theme_constant_override("margin_top", 12) + margin.add_theme_constant_override("margin_bottom", 12) panel.add_child(margin) var column := VBoxContainer.new() @@ -295,39 +361,39 @@ func _build_rewards_panel(parent: Control) -> void: var heading := Label.new() heading.text = "Possible Rewards" heading.add_theme_font_override("font", Brand.font) - heading.add_theme_font_size_override("font_size", 16) + heading.add_theme_font_size_override("font_size", 15) heading.add_theme_color_override("font_color", Brand.text_color(dark)) column.add_child(heading) rewards_list = VBoxContainer.new() - rewards_list.add_theme_constant_override("separation", 10) + rewards_list.add_theme_constant_override("separation", 8) column.add_child(rewards_list) -func _build_footer() -> void: +func _build_footer(parent: Control) -> void: var dark := UIThemeService.is_dark_mode() footer_card = PanelContainer.new() footer_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) - add_child(footer_card) + parent.add_child(footer_card) var margin := MarginContainer.new() - margin.add_theme_constant_override("margin_left", 16) - margin.add_theme_constant_override("margin_right", 16) - margin.add_theme_constant_override("margin_top", 12) - margin.add_theme_constant_override("margin_bottom", 12) + margin.add_theme_constant_override("margin_left", 14) + margin.add_theme_constant_override("margin_right", 14) + margin.add_theme_constant_override("margin_top", 10) + margin.add_theme_constant_override("margin_bottom", 10) footer_card.add_child(margin) var row := HBoxContainer.new() - row.add_theme_constant_override("separation", 14) + row.add_theme_constant_override("separation", 12) margin.add_child(row) footer_icon_panel = PanelContainer.new() - footer_icon_panel.custom_minimum_size = Vector2(36, 36) + footer_icon_panel.custom_minimum_size = Vector2(32, 32) footer_icon_panel.size_flags_vertical = Control.SIZE_SHRINK_CENTER - footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 18)) + footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 16)) footer_icon = TextureRect.new() footer_icon.texture = STAR_ICON - footer_icon.custom_minimum_size = Vector2(18, 18) + footer_icon.custom_minimum_size = Vector2(16, 16) footer_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE footer_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED footer_icon.modulate = Brand.ROYAL_BLUE @@ -341,21 +407,22 @@ func _build_footer() -> void: footer_heading = Label.new() footer_heading.text = "How it works" footer_heading.add_theme_font_override("font", Brand.font) - footer_heading.add_theme_font_size_override("font_size", 15) + footer_heading.add_theme_font_size_override("font_size", 14) footer_heading.add_theme_color_override("font_color", Brand.text_color(dark)) copy.add_child(footer_heading) footer_description = Label.new() footer_description.text = "Spend RZ Coins to spin the slot machine. Win coins, boosts, tokens, and more!" footer_description.add_theme_font_override("font", Brand.font) - footer_description.add_theme_font_size_override("font_size", 12) + footer_description.add_theme_font_size_override("font_size", 11) footer_description.add_theme_color_override("font_color", Brand.muted_color(dark)) footer_description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART copy.add_child(footer_description) row.add_child(copy) -func _refresh_theme_controls() -> void: +func refresh_theme_controls() -> void: var dark := UIThemeService.is_dark_mode() + add_theme_stylebox_override("panel", Brand.content_card_box(dark, true, Brand.ROYAL_BLUE)) subtitle_label.add_theme_color_override("font_color", Brand.muted_color(dark)) win_up_to_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) win_up_to_label.add_theme_color_override("font_color", Brand.text_color(dark)) @@ -363,7 +430,7 @@ func _refresh_theme_controls() -> void: cost_value_label.add_theme_color_override("font_color", Brand.text_color(dark)) auto_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) footer_card.add_theme_stylebox_override("panel", Brand.content_card_box(dark, false)) - footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 18)) + footer_icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, Brand.ROYAL_BLUE, 16)) footer_heading.add_theme_color_override("font_color", Brand.text_color(dark)) footer_description.add_theme_color_override("font_color", Brand.muted_color(dark)) _refresh_rewards() @@ -382,6 +449,8 @@ func _reward_color(key: String) -> Color: func _refresh_rewards() -> void: + if cost_value_label == null: + return var dark := UIThemeService.is_dark_mode() cost_value_label.text = "%d RZ Coins" % CreditsService.spin_cost @@ -408,13 +477,13 @@ func _build_reward_row(dark: bool, reward: Dictionary) -> HBoxContainer: row.add_theme_constant_override("separation", 10) var icon_panel := PanelContainer.new() - icon_panel.custom_minimum_size = Vector2(36, 36) + icon_panel.custom_minimum_size = Vector2(34, 34) icon_panel.size_flags_vertical = Control.SIZE_SHRINK_CENTER - icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, accent, 18)) + icon_panel.add_theme_stylebox_override("panel", Brand.icon_circle_box(dark, accent, 17)) var icon := TextureRect.new() var spec: Dictionary = REWARD_ICONS.get(key, {"texture": SLOT_COIN, "modulate": false}) icon.texture = spec.get("texture") - icon.custom_minimum_size = Vector2(22, 22) + icon.custom_minimum_size = Vector2(20, 20) icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED if spec.get("modulate", false): @@ -431,13 +500,13 @@ func _build_reward_row(dark: bool, reward: Dictionary) -> HBoxContainer: var label := Label.new() label.text = str(reward.get("label", "")) label.add_theme_font_override("font", Brand.font) - label.add_theme_font_size_override("font_size", 13) + label.add_theme_font_size_override("font_size", 12) label.add_theme_color_override("font_color", Brand.text_color(dark)) copy.add_child(label) var description := Label.new() description.text = str(reward.get("description", "")) description.add_theme_font_override("font", Brand.font) - description.add_theme_font_size_override("font_size", 11) + description.add_theme_font_size_override("font_size", 10) description.add_theme_color_override("font_color", Brand.muted_color(dark)) copy.add_child(description) row.add_child(copy) @@ -445,7 +514,7 @@ func _build_reward_row(dark: bool, reward: Dictionary) -> HBoxContainer: var percent_label := Label.new() percent_label.text = "%s%%" % _format_percent(float(reward.get("probability", 0.0)) * 100.0) percent_label.add_theme_font_override("font", Brand.font) - percent_label.add_theme_font_size_override("font_size", 13) + percent_label.add_theme_font_size_override("font_size", 12) percent_label.add_theme_color_override("font_color", accent) row.add_child(percent_label) @@ -453,6 +522,8 @@ func _build_reward_row(dark: bool, reward: Dictionary) -> HBoxContainer: func _refresh_spin_button() -> void: + if spin_button == null: + return var disabled := _spinning or CreditsService.credits < CreditsService.spin_cost spin_button.disabled = disabled lever_button.disabled = disabled @@ -648,6 +719,46 @@ func _style_spin_button() -> void: spin_button.add_theme_color_override("font_disabled_color", Color(Brand.DARK_TEXT, 0.7)) +func _on_title_bar_gui_input(event: InputEvent) -> void: + var touch_event := event as InputEventScreenTouch + if touch_event != null: + drag_active = touch_event.pressed + if drag_active: + drag_offset = touch_event.position + get_viewport().set_input_as_handled() + return + var touch_drag := event as InputEventScreenDrag + if touch_drag != null and drag_active: + global_position += touch_drag.relative + _clamp_to_viewport() + get_viewport().set_input_as_handled() + return + var mouse_event := event as InputEventMouseButton + if mouse_event != null and mouse_event.button_index == MOUSE_BUTTON_LEFT: + drag_active = mouse_event.pressed + if drag_active: + drag_offset = mouse_event.global_position - global_position + get_viewport().set_input_as_handled() + return + var motion := event as InputEventMouseMotion + if motion != null and drag_active: + global_position = motion.global_position - drag_offset + _clamp_to_viewport() + get_viewport().set_input_as_handled() + + +func _on_viewport_size_changed() -> void: + _clamp_to_viewport() + + +func _clamp_to_viewport() -> void: + if not is_inside_tree(): + return + var viewport_size := get_viewport_rect().size + position.x = clampf(position.x, 12.0, maxf(12.0, viewport_size.x - size.x - 12.0)) + position.y = clampf(position.y, 12.0, maxf(12.0, viewport_size.y - size.y - 12.0)) + + func _format_thousands(value: int) -> String: var digits := str(absi(value)) var grouped := ""