diff --git a/app/sovran_systemsos_hub/application.py b/app/sovran_systemsos_hub/application.py index b313ccf..8851bc3 100644 --- a/app/sovran_systemsos_hub/application.py +++ b/app/sovran_systemsos_hub/application.py @@ -67,7 +67,7 @@ REBOOT_COMMAND = [ UPDATE_CHECK_INTERVAL = 1800 -TILE_GRID_WIDTH = 640 +TILE_GRID_WIDTH = 820 # ── Autostart helpers ──────────────────────────────────────────── @@ -495,10 +495,10 @@ class SovranHubWindow(Adw.ApplicationWindow): def _build_ip_bar(self): bar = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, - spacing=24, + spacing=28, halign=Gtk.Align.CENTER, - margin_top=12, - margin_bottom=4, + margin_top=14, + margin_bottom=6, margin_start=24, margin_end=24, css_classes=["ip-bar"], @@ -506,20 +506,20 @@ class SovranHubWindow(Adw.ApplicationWindow): internal_box = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, - spacing=6, + spacing=8, ) internal_icon = Gtk.Image( icon_name="network-wired-symbolic", - pixel_size=16, + pixel_size=18, css_classes=["dim-label"], ) internal_label = Gtk.Label( label="Internal:", - css_classes=["caption", "dim-label"], + css_classes=["ip-label", "dim-label"], ) self._internal_ip_label = Gtk.Label( label="…", - css_classes=["caption", "ip-value"], + css_classes=["ip-value"], selectable=True, ) internal_box.append(internal_icon) @@ -530,20 +530,20 @@ class SovranHubWindow(Adw.ApplicationWindow): external_box = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, - spacing=6, + spacing=8, ) external_icon = Gtk.Image( icon_name="network-server-symbolic", - pixel_size=16, + pixel_size=18, css_classes=["dim-label"], ) external_label = Gtk.Label( label="External:", - css_classes=["caption", "dim-label"], + css_classes=["ip-label", "dim-label"], ) self._external_ip_label = Gtk.Label( label="…", - css_classes=["caption", "ip-value"], + css_classes=["ip-value"], selectable=True, ) external_box.append(external_icon) @@ -556,17 +556,6 @@ class SovranHubWindow(Adw.ApplicationWindow): return bar - def _fetch_ips_once(self): - thread = threading.Thread(target=self._do_fetch_ips, daemon=True) - thread.start() - return False - - def _do_fetch_ips(self): - internal = _get_internal_ip() - GLib.idle_add(self._internal_ip_label.set_label, internal) - external = _get_external_ip() - GLib.idle_add(self._external_ip_label.set_label, external) - # ── Title box ──────────────────────────────────────────────── def _build_title_box(self): @@ -578,17 +567,17 @@ class SovranHubWindow(Adw.ApplicationWindow): ) box.append(Gtk.Label( label="Sovran_SystemsOS Hub", - css_classes=["title"], + css_classes=["hub-title"], )) box.append(Gtk.Label( label=role_label, - css_classes=["caption", "dim-label"], + css_classes=["role-badge", "dim-label"], )) return box # ── Service tiles ──────────────────────────────────────────── - def _build_tiles(self): + def _build_tiles(self): method = self._config.get("command_method", "systemctl") services = self._config.get("services", []) @@ -602,7 +591,6 @@ class SovranHubWindow(Adw.ApplicationWindow): if not entries: continue - # Fixed-width container for label + separator + tiles container = Gtk.Box( orientation=Gtk.Orientation.VERTICAL, halign=Gtk.Align.CENTER, @@ -612,10 +600,10 @@ class SovranHubWindow(Adw.ApplicationWindow): section_label = Gtk.Label( label=cat_label, - css_classes=["title-4"], + css_classes=["section-header"], halign=Gtk.Align.START, - margin_top=20, - margin_bottom=4, + margin_top=24, + margin_bottom=6, margin_start=16, ) container.append(section_label) @@ -624,7 +612,7 @@ class SovranHubWindow(Adw.ApplicationWindow): orientation=Gtk.Orientation.HORIZONTAL, margin_start=16, margin_end=16, - margin_bottom=8, + margin_bottom=10, ) container.append(sep) @@ -633,10 +621,10 @@ class SovranHubWindow(Adw.ApplicationWindow): min_children_per_line=2, selection_mode=Gtk.SelectionMode.NONE, homogeneous=False, - row_spacing=12, - column_spacing=12, + row_spacing=14, + column_spacing=14, margin_top=4, - margin_bottom=8, + margin_bottom=10, margin_start=16, margin_end=16, halign=Gtk.Align.START, diff --git a/app/sovran_systemsos_hub/service_tile.py b/app/sovran_systemsos_hub/service_tile.py index 158b92e..6692970 100644 --- a/app/sovran_systemsos_hub/service_tile.py +++ b/app/sovran_systemsos_hub/service_tile.py @@ -19,7 +19,11 @@ LOADING_STATES = {"reloading", "activating", "deactivating", "maintenance"} ICON_DIR = os.environ.get("SOVRAN_HUB_ICONS", "") ICON_EXTENSIONS = [".svg", ".png"] -TILE_SIZE = 140 +# ── Locked tile dimensions ─────────────────────────────────────── +TILE_W = 180 +TILE_H = 210 +ICON_PX = 48 +LABEL_W = TILE_W - 24 # 12px padding each side class ServiceTile(Gtk.Box): @@ -34,8 +38,7 @@ class ServiceTile(Gtk.Box): css_classes=["card", "sovran-tile"], **kw, ) - # Force exact tile dimensions - self.set_size_request(TILE_SIZE, TILE_SIZE + 30) + self.set_size_request(TILE_W, TILE_H) self.set_hexpand(False) self.set_vexpand(False) @@ -46,38 +49,37 @@ class ServiceTile(Gtk.Box): # ── Icon ───────────────────────────────────────────────── self._logo = Gtk.Image( - pixel_size=36, - margin_top=14, + pixel_size=ICON_PX, + margin_top=18, halign=Gtk.Align.CENTER, ) self._set_logo(icon_name) self.append(self._logo) - # ── Name label (wraps within tile width) ───────────────── + # ── Name label ─────────────────────────────────────────── self._name_label = Gtk.Label( label=name, - css_classes=["heading", "tile-name"], + css_classes=["tile-name"], halign=Gtk.Align.CENTER, justify=Gtk.Justification.CENTER, wrap=True, wrap_mode=Pango.WrapMode.WORD_CHAR, lines=2, ellipsize=Pango.EllipsizeMode.END, - margin_start=6, - margin_end=6, - margin_top=4, + margin_start=12, + margin_end=12, + margin_top=6, ) - # Clamp the label width so it wraps inside the tile - self._name_label.set_size_request(TILE_SIZE - 16, -1) - self._name_label.set_max_width_chars(1) # forces natural width to be small + self._name_label.set_size_request(LABEL_W, -1) + self._name_label.set_max_width_chars(1) self.append(self._name_label) # ── Status label ───────────────────────────────────────── self._status_label = Gtk.Label( label="● …", - css_classes=["caption", "dim-label"], + css_classes=["caption", "tile-status", "dim-label"], halign=Gtk.Align.CENTER, - margin_top=1, + margin_top=2, ) self.append(self._status_label) @@ -85,12 +87,12 @@ class ServiceTile(Gtk.Box): spacer = Gtk.Box(vexpand=True) self.append(spacer) - # ── Controls ───────────────────────────────────────────── + # ── Controls ───────────────���───────────────────────────── controls = Gtk.Box( orientation=Gtk.Orientation.HORIZONTAL, - spacing=8, + spacing=10, halign=Gtk.Align.CENTER, - margin_bottom=10, + margin_bottom=14, ) self._switch = Gtk.Switch(valign=Gtk.Align.CENTER) self._switch.connect("state-set", self._on_toggled) @@ -106,12 +108,11 @@ class ServiceTile(Gtk.Box): controls.append(restart_btn) self.append(controls) - # If the feature is disabled in custom.nix, lock the tile if not self._enabled: self._switch.set_active(False) self._switch.set_sensitive(False) self._status_label.set_label("○ disabled") - self._status_label.set_css_classes(["caption", "disabled-label"]) + self._status_label.set_css_classes(["caption", "tile-status", "disabled-label"]) self._logo.set_opacity(0.35) self.set_tooltip_text(f"{name} is not enabled in custom.nix") @@ -121,7 +122,8 @@ class ServiceTile(Gtk.Box): path = os.path.join(ICON_DIR, f"{icon_name}{ext}") if os.path.isfile(path): try: - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, 36, 36, True) + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( + path, ICON_PX, ICON_PX, True) texture = Gdk.Texture.new_for_pixbuf(pixbuf) self._logo.set_from_paintable(texture) return @@ -145,16 +147,16 @@ class ServiceTile(Gtk.Box): if is_failed: self._status_label.set_label("● failed") - self._status_label.set_css_classes(["caption", "error"]) + self._status_label.set_css_classes(["caption", "tile-status", "error"]) elif is_on: self._status_label.set_label("● running") - self._status_label.set_css_classes(["caption", "success"]) + self._status_label.set_css_classes(["caption", "tile-status", "success"]) elif is_loading: self._status_label.set_label(f"● {active}") - self._status_label.set_css_classes(["caption", "warning"]) + self._status_label.set_css_classes(["caption", "tile-status", "warning"]) else: self._status_label.set_label(f"● {active}") - self._status_label.set_css_classes(["caption", "dim-label"]) + self._status_label.set_css_classes(["caption", "tile-status", "dim-label"]) def _on_toggled(self, switch, state): if not self._enabled: diff --git a/app/style.css b/app/style.css index 6fe1f51..2d4aaf9 100644 --- a/app/style.css +++ b/app/style.css @@ -1,50 +1,82 @@ +/* ── Tile (locked dimensions) ──────────────────────────────── */ .sovran-tile { - border-radius: 16px; + border-radius: 18px; padding: 0px; - min-width: 140px; - max-width: 140px; - min-height: 170px; - max-height: 170px; + min-width: 180px; + max-width: 180px; + min-height: 210px; + max-height: 210px; overflow: hidden; transition: box-shadow 200ms ease-in-out; } .sovran-tile:hover { - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); } + +/* ── Tile text ─────────────────────────────────────────────── */ .tile-name { - font-size: 0.8em; + font-size: 1.0em; + font-weight: bold; } +.tile-status { + font-size: 0.9em; +} + +/* ── Section headers ───────────────────────────────────────── */ +.section-header { + font-size: 1.3em; + font-weight: bold; +} + +/* ── Status colors ─────────────────────────────────────────── */ .success { color: #2ec27e; } .warning { color: #e5a50a; } .error { color: #e01b24; } .disabled-label { color: #888888; font-style: italic; } + +/* ── Header / role ─────────────────────────────────────────── */ +.hub-title { + font-size: 1.2em; + font-weight: bold; +} .role-badge { padding: 2px 8px; border-radius: 4px; - font-size: 0.75em; + font-size: 0.85em; } + +/* ── Update indicator ──────────────────────────────────────── */ .update-badge { color: #2ec27e; - font-size: 1.2em; + font-size: 1.3em; font-weight: bold; } .update-available { background: #2ec27e; color: white; + font-size: 1.0em; } .update-available:hover { background: #26a269; } + +/* ── IP bar ────────────────────────────────────────────────── */ .ip-bar { - padding: 8px 16px; - border-radius: 8px; + padding: 10px 20px; + border-radius: 10px; background: alpha(@card_bg_color, 0.5); } +.ip-label { + font-size: 0.95em; +} .ip-value { font-family: monospace; font-weight: bold; + font-size: 1.0em; color: @accent_color; } + +/* ── Grid container ────────────────────────────────────────── */ .tiles-container { margin-left: auto; margin-right: auto;