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..6431102 100644
--- a/src/AppButton.js
+++ b/src/AppButton.js
@@ -15,36 +15,105 @@ 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 = 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;
+
+// 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 +122,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 +135,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 +220,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 +238,8 @@ export function ShowInFolderButton({ file, window }) {
tooltip: _("View File in File Manager"),
icon_name: "folder-symbolic",
icon_size: 48,
+ icon_size_compact: 32,
+ 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..0e4c257 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,79 @@
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. */
+
+/* 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;
+ 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..ac7795b 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
@@ -27,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;
@@ -60,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;
@@ -82,22 +79,44 @@ Adw.ApplicationWindow window {
};
}
-PopoverMenu menu_popover {
- menu-model: menu_model;
- halign: start;
-}
-
menu menu_model {
item {
label: _("Show App Names");
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..a4f7b5b 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 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;
+
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}"),