From 3fe6965df9e667a1561ba0807b9b1559b516bd47 Mon Sep 17 00:00:00 2001 From: epicmeme546 <178368684+epicmeme546@users.noreply.github.com> Date: Wed, 11 Nov 2026 03:11:09 -0500 Subject: [PATCH] Give the Custom colour scheme a Panel Color for the panels and the title band, and have Button Color change the buttons alone, their orange choices included. Button Color recoloured the list panel, the description box and the title band, and left the orange choices in the rows as they were, so the buttons themselves seemed not to change. It now takes the rows' label cells, their value cells and the orange choices, which come out a lighter shade of the picked colour; the new Panel Color takes the panels, the dividers and the title band. Text on a light button or panel turns dark, and so does the text in choices that come out bright. The colour is kept as custom_panel in launcher.json. Co-Authored-By: Claude Opus 5.5 --- docs/launcher.md | 17 ++++++++-------- docs/releases/v0.7.2.md | 4 ++-- launcher/settings.py | 3 ++- launcher/ui/app.py | 14 ++++++------- launcher/ui/art.py | 44 ++++++++++++++++++++++++++--------------- launcher/ui/selftest.py | 4 ++-- tests/launcher_test.py | 31 ++++++++++++++++++++++++----- 7 files changed, 75 insertions(+), 42 deletions(-) diff --git a/docs/launcher.md b/docs/launcher.md index 3c883870..cdb2334b 100644 --- a/docs/launcher.md +++ b/docs/launcher.md @@ -38,13 +38,14 @@ Customize Launcher (on the home screen) makes the look the player's own: - Color Scheme: Forest (the colours above), Volcano, Lake, Desert, Temple and Shadow. A scheme is the first one with its woods, accents, backdrop and glow turned to other hues (`art.THEMES`, `art.recolor`), so each keeps the same light and dark steps and the same readable text. The window is drawn again in the new colours at once. The names - are plain words, not the game's. Custom, the last one, takes two colours of the player's: Background Color for the - backdrop and Button Color for the buttons, the panels and the title band. Each opens a picker in the description + are plain words, not the game's. Custom, the last one, takes three colours of the player's: Background Color for + the backdrop, Panel Color for the list panel, the description box and the title band, and Button Color for the + rows' cells and the orange choices in them. Each opens a picker in the description box's place: a square of the hue's colours (saturation across, brightness up and down) and a hue bar under it, to click or drag on, Hue, Saturation and Brightness rows for the arrows and the gamepad, and Hex Code, where a code such as `#3A7BD5` is typed. The picked colour stands in for one colour of the first scheme (`art.CUSTOM_SLOTS`) and - the rest of its group follows it, lighter and darker as in the first scheme (`art.follow`); light buttons get dark - text. The window takes the new colour as the mouse moves; Reset brings back the first scheme's colour. + the rest of its part of the window follows it, lighter and darker as in the first scheme (`art.follow`); text on + a light button or panel turns dark. The window takes the new colour as the mouse moves; Reset brings back the first scheme's colour. - Background: a picture of the player's own behind the panels instead of the scheme's backdrop (`launcher/ui/picture.py`: PNG, JPEG, WebP, BMP, GIF or TIFF, read with Pillow). It fills the window below the title band, so none of it is hidden under the band: cut to that shape around its middle and scaled evenly, never stretched, and kept as @@ -75,8 +76,8 @@ Customize Launcher (on the home screen) makes the look the player's own: starts the launcher of `app/current.txt` with `--play`: the game starts as Play starts it, and the process waits for it, so Steam counts the game as running while it is. -The choices are `theme`, `custom_background`, `custom_buttons`, `background`, `background_dim`, `animations` and -`sounds` in `data/launcher.json`. +The choices are `theme`, `custom_background`, `custom_panel`, `custom_buttons`, `background`, `background_dim`, +`animations` and `sounds` in `data/launcher.json`. Navi (`art.NAVI`, 22 x 21 pixels, drawn in whole pixels at every size) is also the icon of `NX2navi.exe`, `NX2navi-Setup.exe`, the window and its taskbar button (`art.navi_ico`, written into the release by @@ -86,8 +87,8 @@ identity (`NX2navi.Launcher`), so the taskbar groups and pins it under NX2navi r Screens (`launcher/ui/app.py`): first run (dump, install folder, Desktop Shortcut, Check for Updates at Start, Update Channel, Begin Installation); the steps of an install, update or rebuild with their progress, the build log's last lines and Cancel, and after a failure Try Again and Copy Report; home (Play, Updates, Game Folder, Saves, -Rebuild the Game, Customize Launcher, Settings); customize (Color Scheme, with Custom Background Color and Button -Color, Background, with a picture set Background Dimming and Remove Background, Animations, Sounds, Add to Steam on +Rebuild the Game, Customize Launcher, Settings); customize (Color Scheme, with Custom Background Color, Panel Color and +Button Color, Background, with a picture set Background Dimming and Remove Background, Animations, Sounds, Add to Steam on Windows, Back); the colour picker (Hue, Saturation, Brightness, Hex Code, Reset, Done); saves (Export Save, Add Log and Settings, Export To, Import a Save, Import a Save Folder, Open Saves Folder, Back); settings (update check, Install Updates Automatically, Hide Launcher While Playing, Keep Build Cache, Update Channel, the folders, Open Logs Folder, Uninstall, Back); uninstall (keep or remove the built game). diff --git a/docs/releases/v0.7.2.md b/docs/releases/v0.7.2.md index af6d3c71..474fdbe3 100644 --- a/docs/releases/v0.7.2.md +++ b/docs/releases/v0.7.2.md @@ -21,8 +21,8 @@ Draft notes for the release after 0.7.1. The version number is not decided yet. - **Add to Steam** in Customize Launcher (Windows): adds the game to your Steam library as *The Legend of Zelda Ocarina of Time*, with Navi as its icon and the title screen's artwork and logo. Starting it from Steam starts the game. Steam restarts once while it is added. -- **Custom color scheme** in Customize Launcher: pick your own background color and button color on a color square - with a hue bar, or type a hex code. +- **Custom color scheme** in Customize Launcher: pick your own background, panel and button colors on a color + square with a hue bar, or type a hex code. ## Fixed diff --git a/launcher/settings.py b/launcher/settings.py index a725c6ad..97020f13 100644 --- a/launcher/settings.py +++ b/launcher/settings.py @@ -17,7 +17,8 @@ DEFAULTS = { 'export_with_log': True, # Export Save adds the game's latest log and nx2navi.ini (for a bug report) 'theme': 'forest', # the colour scheme (launcher/ui/art.py THEMES) 'custom_background': '', # the Custom scheme's backdrop colour, #rrggbb; empty: the first scheme's - 'custom_buttons': '', # the Custom scheme's colour of the buttons, panels and title band + 'custom_panel': '', # the Custom scheme's colour of the panels and the title band + 'custom_buttons': '', # the Custom scheme's colour of the buttons 'background': '', # the picture the player chose as the background; its fitted copy is data/background.npy 'background_dim': 40, # how much that picture is darkened, in percent 'animations': True, # the cursor glides, screens slide in, sparks drift, Navi bobs diff --git a/launcher/ui/app.py b/launcher/ui/app.py index 507f4e17..c7688a70 100644 --- a/launcher/ui/app.py +++ b/launcher/ui/app.py @@ -60,7 +60,9 @@ DEPTH = {'first_run': 0, 'home': 0, 'job': 1, 'saves': 1, 'settings': 1, 'custom PICKER_BOX = (614, 100, 336, 452) SQUARE = (667, 160, 897, 390) BAR = (667, 408, 897, 430) -CUSTOM_LABELS = {'background': 'Background Color', 'buttons': 'Button Color'} +CUSTOM_LABELS = {'background': 'Background Color', 'panel': 'Panel Color', 'buttons': 'Button Color'} +CUSTOM_DESC = {'background': 'The color behind the menus.', 'panel': 'The color of the panels and the top bar.', + 'buttons': 'The color of the buttons.'} DIMS = (0, 20, 40, 60, 80) # Background Dimming, in percent SOUND_LEVELS = (('off', 'Off'), ('low', 'Low'), ('medium', 'Medium'), ('high', 'High')) @@ -980,13 +982,9 @@ class App: desc=('The launcher\'s colours. Custom lets you pick your own.',)), ] if art.theme == 'custom': - rows += [ - Row('value', CUSTOM_LABELS['background'], art.CUSTOM['background'], 'swatch', - on=lambda: self.open_picker('background'), desc=('The color behind the menus.',)), - Row('value', CUSTOM_LABELS['buttons'], art.CUSTOM['buttons'], 'swatch', - on=lambda: self.open_picker('buttons'), - desc=('The color of the buttons, the panels and the top bar.',)), - ] + rows += [Row('value', CUSTOM_LABELS[slot], art.CUSTOM[slot], 'swatch', + on=lambda sl=slot: self.open_picker(sl), desc=(CUSTOM_DESC[slot],)) + for slot in art.CUSTOM_SLOTS] rows += [ Row('value', 'Background', Path(self.settings['background']).name if custom else 'Choose a Picture', 'arrow', on=self.choose_background, desc=bg_desc), diff --git a/launcher/ui/art.py b/launcher/ui/art.py index 36b9746d..dd6bd1e6 100644 --- a/launcher/ui/art.py +++ b/launcher/ui/art.py @@ -7,9 +7,9 @@ shape is a picture; flat rectangles and text stay canvas items. Colours follow the game's options menu (docs/launcher.md, "Look"). Sizes are in logical pixels times the display scale s. PALETTE holds the colours of the colour scheme in use: set_theme fills it from THEMES, each scheme the -first one with its woods, accents, backdrop and glow turned to other hues. The Custom scheme takes two colours the -player picks on a colour square (color_square, hue_bar): one for the backdrop, one for the buttons, panels and title -band. +first one with its woods, accents, backdrop and glow turned to other hues. The Custom scheme takes three colours the +player picks on a colour square (color_square, hue_bar): one for the backdrop, one for the panels and the title band, +one for the buttons. """ import base64 import colorsys @@ -81,11 +81,21 @@ THEMES = { } DEFAULT_THEME = 'forest' -# The Custom scheme: each colour the player picks takes the place of one colour of the first scheme, and the rest of -# that colour's group follows it, lighter and darker as in the first scheme (follow). Its two colours start as the -# first scheme's. -CUSTOM_SLOTS = {'background': ('scene', 'tint_mid'), 'buttons': ('wood', 'label_mid')} -CUSTOM_DEFAULT = {slot: BASE[ref] for slot, (_, ref) in CUSTOM_SLOTS.items()} +# The Custom scheme: each colour the player picks takes the place of one colour of the first scheme (ref), and the +# rest of that colour's part of the window follows it, lighter and darker as in the first scheme (follow). Each part: +# (ref, the colours that follow it, the text on it, which turns dark when the picked colour is light). The buttons are +# the rows' label cells, their value cells and the orange choices in them; the panel is the list panel, the +# description box, the dividers and the title band. The colours start as the first scheme's. +CUSTOM_SLOTS = { + 'background': ('tint_mid', GROUPS['scene'], ()), + 'panel': ('panel', ('panel', 'desc', 'desc_border', 'line', 'dot', 'filigree', 'button_hover', 'wood_0', 'wood_1', + 'wood_2', 'wood_3', 'band_line_dark', 'band_line_light', 'band_shadow', 'frame'), + ('desc_text', 'muted', 'band_text', 'status', 'log_text')), + 'buttons': ('label_mid', ('label_top', 'label_mid', 'label_bottom', 'label_border', 'ring', 'value', 'value_border', + 'text_shadow') + GROUPS['accent'], + ('text', 'text_bright', 'value_text', 'on_text', 'icon_frame', 'icon_fill', 'chevron_dim')), +} +CUSTOM_DEFAULT = {slot: BASE[ref] for slot, (ref, _, _) in CUSTOM_SLOTS.items()} CUSTOM = dict(CUSTOM_DEFAULT) HEX = re.compile(r'^#[0-9a-f]{6}$') @@ -134,20 +144,22 @@ def luminance(hexcolor): def custom_palette(colors): """The Custom scheme's colours from {slot: '#rrggbb'} (CUSTOM_SLOTS); a slot left out or not a colour keeps the - first scheme's. Light buttons get dark text, so the labels stay readable on them.""" + first scheme's. Text on a light button or panel turns dark, so it stays readable, and its shadow light.""" pal = dict(BASE) - for slot, (group, ref) in CUSTOM_SLOTS.items(): + for slot, (ref, keys, text) in CUSTOM_SLOTS.items(): c = str((colors or {}).get(slot) or '').lower() if not HEX.match(c) or c == BASE[ref]: # the first scheme's own colour: its colours as written continue - for k in GROUPS[group]: + for k in keys: pal[k] = follow(BASE[k], BASE[ref], c) - if slot == 'buttons' and luminance(c) > 0.5: + if text and luminance(c) > 0.5: h = hls(c)[0] * 360 - for k in GROUPS['text']: - pal[k] = recolor(BASE[k], h, 0.5, 0.18) - pal['text_shadow'] = follow(BASE['label_top'], BASE[ref], c) - pal['chevron_dim'] = recolor(BASE['chevron_dim'], h, 0.5, 0.3) + for k in text: + pal[k] = recolor(BASE[k], h, 0.5, 0.3 if k == 'chevron_dim' else 0.18) + shadow = 'text_shadow' if slot == 'buttons' else 'band_shadow' + pal[shadow] = follow(BASE['label_top'], BASE[ref], c) + if slot == 'buttons' and luminance(pal['orange']) > 0.65: # the choices come out lighter than the cells + pal['on_text'] = recolor(BASE['on_text'], hls(c)[0] * 360, 0.5, 0.18) return pal diff --git a/launcher/ui/selftest.py b/launcher/ui/selftest.py index 38e832b4..7eeec6c1 100644 --- a/launcher/ui/selftest.py +++ b/launcher/ui/selftest.py @@ -205,8 +205,8 @@ def states(app): def custom_picker(): # the Custom scheme with two picked colours, and its picker on the button colour from . import art - was = {k: s.get(k) for k in ('theme', 'custom_background', 'custom_buttons')} - s.update(theme='custom', custom_background='#3a1d5c', custom_buttons='#2f6fb0') + was = {k: s.get(k) for k in ('theme', 'custom_background', 'custom_panel', 'custom_buttons')} + s.update(theme='custom', custom_background='#3a1d5c', custom_panel='#24324a', custom_buttons='#2f6fb0') app.apply_look() app.open_picker('buttons') s.update(was) diff --git a/tests/launcher_test.py b/tests/launcher_test.py index a997f7fd..12b55009 100644 --- a/tests/launcher_test.py +++ b/tests/launcher_test.py @@ -2713,23 +2713,44 @@ class LookTest(Tmp): def test_the_custom_scheme(self): art = self.art self.assertEqual(art.custom_palette({}), art.BASE) # nothing picked yet: the first scheme's colours - pal = art.custom_palette({'background': '#3A7BD5', 'buttons': '#802020'}) + slots = art.CUSTOM_SLOTS + self.assertEqual(list(slots), ['background', 'panel', 'buttons']) + parts = [k for _, keys, _ in slots.values() for k in keys] + self.assertEqual(len(parts), len(set(parts)), 'a colour in two parts') + self.assertEqual(set(parts), set(art.GROUPS['scene'] + art.GROUPS['wood'] + art.GROUPS['accent'])) + pal = art.custom_palette({'background': '#3A7BD5', 'panel': '#203040', 'buttons': '#802020'}) self.assertEqual(pal['tint_mid'], '#3a7bd5') # the picked colours exactly where they stand in + self.assertEqual(pal['panel'], '#203040') self.assertEqual(pal['label_mid'], '#802020') - self.assertEqual(pal['orange'], art.BASE['orange']) # the other groups stay - for group, ref in (('scene', 'tint_mid'), ('wood', 'label_mid')): - for k in art.GROUPS[group]: # lighter than the picked colour where the first scheme's is lighter + only = art.custom_palette({'buttons': '#802020'}) # Button Color: the buttons and nothing else + self.assertLessEqual({k for k in art.BASE if only[k] != art.BASE[k]}, set(slots['buttons'][1])) + for k in ('label_top', 'label_bottom', 'value', 'orange', 'orange_light'): # the orange choices too + self.assertNotEqual(only[k], art.BASE[k], k) + for k in ('panel', 'desc', 'wood_1', 'window', 'tint_mid'): # not the panels, the title band, the backdrop + self.assertEqual(only[k], art.BASE[k], k) + self.assertEqual(round(art.hls(only['orange'])[0] * 360), 0) # the choices in the picked hue, lighter + self.assertGreater(art.hls(only['orange'])[1], art.hls(only['label_mid'])[1]) + panel = art.custom_palette({'panel': '#203040'}) + self.assertLessEqual({k for k in art.BASE if panel[k] != art.BASE[k]}, set(slots['panel'][1])) + self.assertEqual(panel['label_mid'], art.BASE['label_mid']) + for slot, (ref, keys, _) in slots.items(): + for k in keys: # lighter than the picked colour where the first scheme's is lighter if art.hls(art.BASE[k])[1] > art.hls(art.BASE[ref])[1] + 0.02: self.assertGreater(art.hls(pal[k])[1], art.hls(pal[ref])[1], k) self.assertGreater(art.luminance(pal['text']), 0.6) # light text on dark buttons light = art.custom_palette({'buttons': '#f0e0a0'}) self.assertLess(art.luminance(light['text']), 0.3) # and dark text on light ones + self.assertGreater(art.luminance(light['desc_text']), 0.6) # the description box's text stays as it was + light = art.custom_palette({'panel': '#f0e0a0'}) + self.assertLess(art.luminance(light['desc_text']), 0.3) # dark on a light panel + self.assertGreater(art.luminance(light['text']), 0.6) self.assertEqual(art.custom_palette({'buttons': 'red', 'background': None}), art.BASE) held = art.PALETTE try: self.assertEqual(art.set_theme('custom', {'buttons': '#802020', 'other': '#000000'}), 'custom') self.assertEqual(held['label_mid'], '#802020') - self.assertEqual(art.CUSTOM, {'background': art.BASE['tint_mid'], 'buttons': '#802020'}) + self.assertEqual(art.CUSTOM, {'background': art.BASE['tint_mid'], 'panel': art.BASE['panel'], + 'buttons': '#802020'}) art.set_theme('custom', {}) self.assertEqual(dict(held), art.BASE) finally: