From 9e9ddfa914bc4fbae0f17c3372476bb25c2480c5 Mon Sep 17 00:00:00 2001 From: George Dumitrescu Date: Mon, 27 Jul 2026 12:41:34 +0300 Subject: [PATCH 1/2] Follow the system style, add a compact mode and show the 1-9 keys Junction forced Adw.ColorScheme.FORCE_DARK and hardcoded its colors, so it was a dark window whatever the desktop asked for, and on a light style the labels and window controls turned dark-on-dark. Fixes #96. Style: - The color scheme now defaults to "default", so libadwaita follows the org.freedesktop.appearance portal - color scheme, accent color and contrast. GNOME, KDE Plasma and others implement it. - A "Style" section in the menu (Follow System / Light / Dark) overrides it, stored in a new "color-scheme" key whose values match AdwColorScheme. - The hardcoded #353433, #1e1e1e, white and black are replaced with @popover_bg_color, @view_bg_color, @borders and @accent_*, so light, dark and high contrast all render correctly. - The focused tile is tinted with the accent color instead of a fixed white wash that was invisible on light backgrounds. - "Test Junction" in the welcome window no longer forces white text, which was invisible on a light background. Compact mode: - A "Compact Mode" menu toggle backed by a new "compact" key shrinks icons (92 -> 48), tiles (134 -> 80), padding and the window (772x218 -> 560x140). - Compact tiles are icons only whatever "Show App Names" says - a name ellipsizes to uselessness under an 80px tile. The name stays in the tooltip. Keyboard: - Each of the first nine tiles now shows the digit that opens it. The shortcut already existed but was only discoverable from the Keyboard Shortcuts window, and compact mode hides names entirely. - 1-9 on the numeric keypad work too, not just the number row. --- data/re.sonny.Junction.gschema.xml | 13 +++++ src/AppButton.blp | 38 ++++++++----- src/AppButton.js | 91 ++++++++++++++++++++++++++---- src/application.js | 13 ++++- src/style.css | 89 +++++++++++++++++++++++++---- src/window.blp | 32 +++++++++-- src/window.js | 45 +++++++++++++-- 7 files changed, 279 insertions(+), 42 deletions(-) diff --git a/data/re.sonny.Junction.gschema.xml b/data/re.sonny.Junction.gschema.xml index c09356e..fb27ba8 100644 --- a/data/re.sonny.Junction.gschema.xml +++ b/data/re.sonny.Junction.gschema.xml @@ -1,8 +1,21 @@ + + + + + + false + + false + + + 'default' + diff --git a/src/AppButton.blp b/src/AppButton.blp index f4cd78d..46e81d3 100644 --- a/src/AppButton.blp +++ b/src/AppButton.blp @@ -10,22 +10,34 @@ Button button { styles ["flat"] - Box box { - orientation: vertical; - valign: center; - halign: center; + Overlay { + Box box { + orientation: vertical; + valign: center; + halign: center; - Image image { - pixel-size: 92; - width-request: 92; - height-request: 92; - styles ["icon-dropshadow"] + Image image { + pixel-size: 92; + width-request: 92; + height-request: 92; + styles ["icon-dropshadow"] + } + + Label label { + ellipsize: end; + max-width-chars: 10; + margin-top: 6; + } } - Label label { - ellipsize: end; - max-width-chars: 10; - margin-top: 6; + // the 1...9 key that opens this app, set in AppButton.js + [overlay] + Label position { + halign: start; + valign: start; + visible: false; + can-target: false; + styles ["position"] } } } diff --git a/src/AppButton.js b/src/AppButton.js index 2837bd7..61970bd 100644 --- a/src/AppButton.js +++ b/src/AppButton.js @@ -15,36 +15,104 @@ import Interface from "./AppButton.blp" with { type: "uri" }; const portal = new Xdp.Portal(); Gio._promisify(portal, "open_directory", "open_directory_finish"); +const ICON_SIZE = 92; +const ICON_SIZE_COMPACT = 48; +const TILE_SIZE = 134; +const TILE_SIZE_COMPACT = 80; +// only 1...9 can be typed, see getButtonForKeyval in window.js +const MAX_POSITION = 9; + +// Show which of the 1...9 keys opens this app. Without it the shortcut is +// only discoverable from the Keyboard Shortcuts window. +function setPosition(label, position) { + if (!position || position > MAX_POSITION) return; + label.label = String(position); + label.visible = true; +} + +// GtkImage:pixel-size and size requests cannot be set from CSS, so the +// compact sizes are applied here rather than in style.css. +function bindCompact({ + button, + image, + label, + icon_size = ICON_SIZE, + icon_size_compact = ICON_SIZE_COMPACT, +}) { + function update() { + const compact = settings.get_boolean("compact"); + + const size = compact ? icon_size_compact : icon_size; + image.set_pixel_size(size); + image.set_size_request(size, size); + + const tile = compact ? TILE_SIZE_COMPACT : TILE_SIZE; + button.set_size_request(tile, tile); + + // A name doesn't fit under a compact tile - it ends up ellipsized to + // uselessness - so compact mode is icons only, whatever "Show App Names" + // is set to. The name is still in the tooltip. + label.visible = !compact && settings.get_boolean("show-app-names"); + } + + const handlers = [ + settings.connect("changed::compact", update), + settings.connect("changed::show-app-names", update), + ]; + button.connect("destroy", () => { + for (const handler of handlers) settings.disconnect(handler); + }); + update(); +} + export function TileButton({ label, tooltip = label, icon_name, icon_size, + icon_size_compact, + position, onClicked, }) { const { button, label: glabel, image, + position: position_label, } = build(Interface, { onClicked, }); button.set_tooltip_text(tooltip); glabel.label = label; - glabel.visible = false; - settings.bind("show-app-names", glabel, "visible", Gio.SettingsBindFlags.GET); + setPosition(position_label, position); image.set_from_icon_name(icon_name); - if (icon_size) { - image.set_pixel_size(icon_size); - } + bindCompact({ + button, + image, + label: glabel, + icon_size, + icon_size_compact, + }); return button; } -export default function AppButton({ appInfo, content_type, entry, window }) { - const { button, label, image, box } = build(Interface, { +export default function AppButton({ + appInfo, + content_type, + entry, + window, + position, +}) { + const { + button, + label, + image, + box, + position: position_label, + } = build(Interface, { onClicked() { open(true); }, @@ -53,8 +121,7 @@ export default function AppButton({ appInfo, content_type, entry, window }) { const name = appInfo.get_display_name(); button.set_tooltip_text(name); label.label = name; - label.visible = false; - settings.bind("show-app-names", label, "visible", Gio.SettingsBindFlags.GET); + setPosition(position_label, position); const menu = new Gio.Menu(); const popoverMenu = Gtk.PopoverMenu.new_from_model(menu); @@ -67,6 +134,8 @@ export default function AppButton({ appInfo, content_type, entry, window }) { image.set_from_file(getIconFilename(icon.get_file().get_path())); } + bindCompact({ button, image, label }); + function open(close_on_success) { const success = openWithApplication({ appInfo, @@ -150,7 +219,7 @@ export default function AppButton({ appInfo, content_type, entry, window }) { return button; } -export function ShowInFolderButton({ file, window }) { +export function ShowInFolderButton({ file, window, position }) { function onClicked() { portal .open_directory( @@ -168,6 +237,8 @@ export function ShowInFolderButton({ file, window }) { tooltip: _("View File in File Manager"), icon_name: "folder-symbolic", icon_size: 48, + icon_size_compact: 24, + position, onClicked, }); } diff --git a/src/application.js b/src/application.js index 4dd368b..c1ce7de 100644 --- a/src/application.js +++ b/src/application.js @@ -8,6 +8,7 @@ import Window from "./window.js"; import Welcome from "./welcome.js"; import About from "./about.js"; import ShortcutsWindow from "./ShortcutsWindow.js"; +import { settings } from "./common.js"; import "./style.css"; @@ -46,7 +47,15 @@ export default function Application() { } application.connect("startup", () => { - Adw.StyleManager.get_default().set_color_scheme(Adw.ColorScheme.FORCE_DARK); + // "default" lets the desktop decide - the color scheme, accent color and + // contrast preference are read from the org.freedesktop.appearance portal, + // which GNOME, KDE Plasma and others implement. + const style_manager = Adw.StyleManager.get_default(); + function updateColorScheme() { + style_manager.set_color_scheme(settings.get_enum("color-scheme")); + } + settings.connect("changed::color-scheme", updateColorScheme); + updateColorScheme(); }); // FIXME: Cannot deal with mailto:, xmpp:, ... URIs @@ -124,6 +133,8 @@ export default function Application() { application.add_action(quit); application.set_accels_for_action("app.quit", ["Q"]); + application.add_action(settings.create_action("color-scheme")); + application.set_accels_for_action("window.close", ["W", "Escape"]); application.set_accels_for_action("win.copy", ["C"]); diff --git a/src/style.css b/src/style.css index 19612a1..ca4e134 100644 --- a/src/style.css +++ b/src/style.css @@ -2,14 +2,13 @@ padding: 68px; } -.welcome button.link { - color: white; -} - .main { border-radius: 30px; background: none; - background-color: #353433; + /* Junction floats above the desktop like a popover rather than sitting on + it like a document window, so it follows the popover background - which + is also a touch lighter than the window background in dark styles. */ + background-color: @popover_bg_color; border: none; box-shadow: none; } @@ -59,11 +58,17 @@ font-size: 0.6rem; } +.main .list button:hover { + background-color: alpha(currentColor, 0.08); +} + +/* Tiles are reachable with the 1-9 keys, so the focused tile has to be + obvious. Tint it with the desktop accent color instead of a fixed white + wash, which was invisible on light backgrounds. */ .main .list button:focus { outline: none; - /* This is libadwaita background for active */ - /* https://gitlab.gnome.org/GNOME/libadwaita/-/blob/main/src/stylesheet/widgets/_buttons.scss#L3 */ - background-color: alpha(white, 0.3); + background-color: alpha(@accent_bg_color, 0.25); + box-shadow: inset 0 0 0 2px @accent_color; } .main .list button label { @@ -71,8 +76,72 @@ font-weight: 400; } +/* The 1...9 key that opens each app */ +.main .list button .position { + font-size: 11px; + font-weight: 700; + min-width: 18px; + min-height: 18px; + padding: 0 4px; + margin: 4px; + border-radius: 9px; + background-color: alpha(currentColor, 0.12); +} + +.main .list button:focus .position { + background-color: @accent_bg_color; + color: @accent_fg_color; +} + .main entry.uri { - background-color: #1e1e1e; + background-color: @view_bg_color; border-radius: 18px; - border: solid 1px black; + border: solid 1px @borders; + box-shadow: none; +} + +/* Compact mode - smaller icons, tiles and padding so the window takes up + much less room. */ +.main.compact .list { + padding-top: 6px; + padding-left: 6px; + padding-right: 6px; +} + +.main.compact .bar { + padding: 6px; +} + +.main.compact .list button { + border-radius: 12px; + padding: 6px; +} + +.main.compact .list button label { + font-size: 12px; +} + +.main.compact .list button .position { + font-size: 9px; + min-width: 14px; + min-height: 14px; + padding: 0 3px; + margin: 1px; + border-radius: 7px; +} + +.main.compact entry.uri { + border-radius: 12px; +} + +/* High contrast: the accent wash alone is too subtle, so lean on the + border and a solid focus ring. */ +@media (prefers-contrast: more) { + .main entry.uri { + border-width: 2px; + } + + .main .list button:focus { + box-shadow: inset 0 0 0 3px @accent_color; + } } diff --git a/src/window.blp b/src/window.blp index b6d4d1e..425c5b4 100644 --- a/src/window.blp +++ b/src/window.blp @@ -2,10 +2,7 @@ using Gtk 4.0; using Adw 1; Adw.ApplicationWindow window { - default-width: 772; - default-height: 218; - width-request: 360; - height-request: 218; + // the size is set in window.js, it depends on the "compact" setting styles ["main"] // portrait @@ -93,11 +90,38 @@ menu menu_model { action: "win.show-app-names"; } + item { + label: _("Compact Mode"); + action: "win.compact"; + } + item { label: _("Copy to Clipboard"); action: "win.copy"; } + section { + label: _("Style"); + + item { + label: _("Follow System"); + action: "app.color-scheme"; + target: "default"; + } + + item { + label: _("Light"); + action: "app.color-scheme"; + target: "force-light"; + } + + item { + label: _("Dark"); + action: "app.color-scheme"; + target: "force-dark"; + } + } + section { item { label: _("Keyboard Shortcuts"); diff --git a/src/window.js b/src/window.js index 7d75f8b..d3bc4c2 100644 --- a/src/window.js +++ b/src/window.js @@ -11,12 +11,43 @@ import AppButton, { ShowInFolderButton } from "./AppButton.js"; import { settings } from "./common.js"; import Interface from "./window.blp" with { type: "uri" }; +const WINDOW_WIDTH = 772; +const WINDOW_HEIGHT = 218; +const WINDOW_WIDTH_COMPACT = 560; +// tile 80 + .list padding 2x6 + the bottom bar, the way 218 is +// tile 134 + .list padding 2x12 + the bottom bar +const WINDOW_HEIGHT_COMPACT = 140; +const WINDOW_WIDTH_MIN = 360; + export default function Window({ application, file }) { const { window, list, entry } = build(Interface); if (__DEV__) window.add_css_class("devel"); window.set_application(application); + // The tiles shrink in compact mode, but the window keeps whatever size it + // already has, so it is resized explicitly on top of the .compact styles. + function applyCompact() { + const compact = settings.get_boolean("compact"); + + if (compact) window.add_css_class("compact"); + else window.remove_css_class("compact"); + + window.set_size_request( + WINDOW_WIDTH_MIN, + compact ? WINDOW_HEIGHT_COMPACT : WINDOW_HEIGHT, + ); + window.set_default_size( + compact ? WINDOW_WIDTH_COMPACT : WINDOW_WIDTH, + compact ? WINDOW_HEIGHT_COMPACT : WINDOW_HEIGHT, + ); + } + const compact_handler = settings.connect("changed::compact", applyCompact); + window.connect("destroy", () => { + settings.disconnect(compact_handler); + }); + applyCompact(); + const { content_type, resource, scheme } = readResource(file); Entry({ @@ -29,12 +60,13 @@ export default function Window({ application, file }) { const options = []; - applications.forEach((appInfo) => { + applications.forEach((appInfo, index) => { const button = AppButton({ appInfo, content_type, entry, window, + position: index + 1, }); appInfo.button = button; options.push(button); @@ -54,6 +86,7 @@ export default function Window({ application, file }) { file, entry, window, + position: options.length + 1, }); options.push(button); list.append( @@ -66,9 +99,10 @@ export default function Window({ application, file }) { function getButtonForKeyval(keyval) { const keyname = Gdk.keyval_name(keyval); - // Is not 0...9 - if (!/^\d$/.test(keyname)) return null; - const n = +keyname; + // Is not 0...9, on the number row or on the numeric keypad + const digit = /^(?:KP_)?(\d)$/.exec(keyname); + if (!digit) return null; + const n = +digit[1]; return options[n - 1]; } @@ -100,6 +134,9 @@ export default function Window({ application, file }) { const toggleAppNames = settings.create_action("show-app-names"); window.add_action(toggleAppNames); + const toggleCompact = settings.create_action("compact"); + window.add_action(toggleCompact); + const run_action = new Gio.SimpleAction({ name: "run_action", parameter_type: new GLib.VariantType("a{ss}"), From ffe673256a9483ab924382239390227571a3e4f2 Mon Sep 17 00:00:00 2001 From: George Dumitrescu Date: Mon, 27 Jul 2026 14:09:12 +0300 Subject: [PATCH 2/2] Give each menu button its own popover, tune compact mode Both MenuButtons pointed at a single shared PopoverMenu instance. A GtkPopover can only have one parent, so GTK reparented it and logged gtk_widget_set_parent criticals on every launch. The visible symptoms were a stray arrow pointing at the hidden header button, and the menu opening on its own. Using menu-model: on each button gives each one a correctly parented popover built from the same GMenu, and the criticals go away. Compact mode, after trying it on a real desktop: - Icons 48 -> 64 and tiles 80px, so the tile hugs the icon instead of leaving 16px of dead space around it. The folder icon follows, 24 -> 32. - Window corner radius 30px -> 18px when compact. 30px is a big bite out of a 140px tall window, and the curve cut into the first tile. --- src/AppButton.js | 5 +++-- src/style.css | 7 +++++++ src/window.blp | 9 ++------- src/window.js | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/AppButton.js b/src/AppButton.js index 61970bd..6431102 100644 --- a/src/AppButton.js +++ b/src/AppButton.js @@ -16,8 +16,9 @@ const portal = new Xdp.Portal(); Gio._promisify(portal, "open_directory", "open_directory_finish"); const ICON_SIZE = 92; -const ICON_SIZE_COMPACT = 48; +const ICON_SIZE_COMPACT = 64; const TILE_SIZE = 134; +// icon + 8px of breathing room on each side, so the tile hugs the icon const TILE_SIZE_COMPACT = 80; // only 1...9 can be typed, see getButtonForKeyval in window.js const MAX_POSITION = 9; @@ -237,7 +238,7 @@ export function ShowInFolderButton({ file, window, position }) { tooltip: _("View File in File Manager"), icon_name: "folder-symbolic", icon_size: 48, - icon_size_compact: 24, + icon_size_compact: 32, position, onClicked, }); diff --git a/src/style.css b/src/style.css index ca4e134..0e4c257 100644 --- a/src/style.css +++ b/src/style.css @@ -102,6 +102,13 @@ /* Compact mode - smaller icons, tiles and padding so the window takes up much less room. */ + +/* 30px is a big bite out of a 140px tall window - the corner curves into + the first tile and pushes it inwards. */ +.main.compact { + border-radius: 18px; +} + .main.compact .list { padding-top: 6px; padding-left: 6px; diff --git a/src/window.blp b/src/window.blp index 425c5b4..ac7795b 100644 --- a/src/window.blp +++ b/src/window.blp @@ -24,7 +24,7 @@ Adw.ApplicationWindow window { Adw.HeaderBar header_bar { [start] MenuButton { - popover: menu_popover; + menu-model: menu_model; icon-name: "open-menu-symbolic"; tooltip-text: _("Main Menu"); valign: center; @@ -57,7 +57,7 @@ Adw.ApplicationWindow window { styles ["bar"] MenuButton button_menu_bottom { - popover: menu_popover; + menu-model: menu_model; icon-name: "open-menu-symbolic"; tooltip-text: _("Main Menu"); valign: center; @@ -79,11 +79,6 @@ Adw.ApplicationWindow window { }; } -PopoverMenu menu_popover { - menu-model: menu_model; - halign: start; -} - menu menu_model { item { label: _("Show App Names"); diff --git a/src/window.js b/src/window.js index d3bc4c2..a4f7b5b 100644 --- a/src/window.js +++ b/src/window.js @@ -14,7 +14,7 @@ import Interface from "./window.blp" with { type: "uri" }; const WINDOW_WIDTH = 772; const WINDOW_HEIGHT = 218; const WINDOW_WIDTH_COMPACT = 560; -// tile 80 + .list padding 2x6 + the bottom bar, the way 218 is +// tile 80 + .list padding 2x6 + the 48px bottom bar, the way 218 is // tile 134 + .list padding 2x12 + the bottom bar const WINDOW_HEIGHT_COMPACT = 140; const WINDOW_WIDTH_MIN = 360;