vm-manager: add empty list screen

This commit is contained in:
Johannes Kirschbauer 2024-04-23 16:16:48 +02:00
parent 1366d0bcf6
commit 5f5155023c
Signed by: hsjobeki
SSH Key Fingerprint: SHA256:vX3utDqig7Ph5L0JPv87ZTPb/w7cMzREKVZzzLFg9qU
6 changed files with 100 additions and 31 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -1,6 +1,6 @@
import logging
from collections.abc import Callable
from typing import Any, Generic, TypeVar
from typing import Any, Generic, TypeVar, ClassVar
import gi
@ -24,6 +24,10 @@ class GKVStore(GObject.GObject, Gio.ListModel, Generic[K, V]):
This class could be optimized by having the objects remember their position in the list.
"""
__gsignals__: ClassVar = {
"is_ready": (GObject.SignalFlags.RUN_FIRST, None, []),
}
def __init__(self, gtype: type[V], key_gen: Callable[[V], K]) -> None:
super().__init__()
self.gtype = gtype

View File

@ -0,0 +1,70 @@
import logging
from typing import Callable, Optional, TypeVar
import gi
from clan_vm_manager import assets
gi.require_version("Adw", "1")
from gi.repository import Adw, Gio, GObject, Gtk, GdkPixbuf
log = logging.getLogger(__name__)
ListItem = TypeVar("ListItem", bound=GObject.Object)
CustomStore = TypeVar("CustomStore", bound=Gio.ListModel)
class EmptySplash(Gtk.Box):
def __init__(self, on_join: Callable[[str], None]) -> None:
super().__init__(orientation=Gtk.Orientation.VERTICAL)
self.on_join = on_join
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
clan_icon = self.load_image(str(assets.get_asset("clan_black_notext.png")))
if clan_icon:
image = Gtk.Image.new_from_pixbuf(clan_icon)
else:
image = Gtk.Image.new_from_icon_name("image-missing")
# same as the clamp
image.set_pixel_size(400)
image.set_opacity(0.5)
image.set_margin_top(20)
image.set_margin_bottom(10)
vbox.append(image)
empty_label = Gtk.Label(label="Welcome to Clan! Join your first clan.")
join_entry = Gtk.Entry()
join_entry.set_placeholder_text("clan://<url>")
join_entry.set_hexpand(True)
join_button = Gtk.Button(label="Join")
join_button.connect("clicked", self._on_join, join_entry)
clamp = Adw.Clamp()
clamp.set_maximum_size(400)
vbox.append(empty_label)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
hbox.append(join_entry)
hbox.append(join_button)
vbox.append(hbox)
clamp.set_child(vbox)
self.append(clamp)
def load_image(self, file_path: str) -> Optional[GdkPixbuf.Pixbuf]:
try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
return pixbuf
except Exception as e:
log.error(f"Failed to load image: {e}")
return None
def _on_join(self, button: Gtk.Button, entry: Gtk.Entry) -> None:
"""
Callback for the join button
Extracts the text from the entry and calls the on_join callback
"""
log.info(f"Splash screen: Joining {entry.get_text()}")
self.on_join(entry.get_text())

View File

@ -7,8 +7,9 @@ from typing import Any, TypeVar
import gi
from clan_cli.clan_uri import ClanURI
from clan_vm_manager import assets
from clan_vm_manager.components.gkvstore import GKVStore
from clan_vm_manager.components.interfaces import ClanConfig
from clan_vm_manager.components.list_splash import EmptySplash
from clan_vm_manager.components.vmobj import VMObject
from clan_vm_manager.singletons.toast import (
LogToast,
@ -73,43 +74,29 @@ class ClanList(Gtk.Box):
self.join_boxed_list.add_css_class("join-list")
self.append(self.join_boxed_list)
clan_store = ClanStore.use().clan_store
clan_store.connect("is_ready", self.display_splash)
self.group_list = create_boxed_list(
model=ClanStore.use().clan_store, render_row=self.render_group_row
model=clan_store, render_row=self.render_group_row
)
self.group_list.add_css_class("group-list")
self.append(self.group_list)
# LIST SPLASH
clan_icon = assets.get_asset("clan.svg")
self.splash = EmptySplash(on_join=lambda x: self.on_join_request(x, x))
if not icon_path:
return ico_buffer
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, icon_size, icon_size)
empty_label = Gtk.Label(label="Welcome to Clan! Join your first clan.")
join_entry = Gtk.Entry()
join_entry.set_placeholder_text("clan://<url>")
join_entry.set_hexpand(True)
join_button = Gtk.Button(label="Join")
join_button.connect("clicked", lambda x: (), join_entry)
clamp = Adw.Clamp()
clamp.set_maximum_size(400)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
vbox.append(empty_label)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
hbox.append(join_entry)
hbox.append(join_button)
vbox.append(hbox)
clamp.set_child(vbox)
self.append(clamp)
def display_splash(self, source: GKVStore) -> None:
print("Displaying splash")
if (
ClanStore.use().clan_store.get_n_items() == 0
and JoinList.use().list_store.get_n_items() == 0
):
self.append(self.splash)
def render_group_row(
self, boxed_list: Gtk.ListBox, vm_store: VMStore
) -> Gtk.Widget:
self.remove(self.splash)
vm = vm_store.first()
log.debug("Rendering group row for %s", vm.data.flake.flake_url)

View File

@ -1,5 +1,6 @@
import logging
import threading
from typing import Callable
import gi
from clan_cli.history.list import list_history
@ -41,7 +42,9 @@ class MainWindow(Adw.ApplicationWindow):
self.tray_icon: TrayIcon = TrayIcon(app)
# Initialize all ClanStore
threading.Thread(target=self._populate_vms).start()
threading.Thread(
target=self._populate_vms, args=[self._set_clan_store_ready]
).start()
# Initialize all views
stack_view = ViewStack.use().view
@ -65,12 +68,17 @@ class MainWindow(Adw.ApplicationWindow):
self.connect("destroy", self.on_destroy)
def _populate_vms(self) -> None:
def _set_clan_store_ready(self) -> None:
ClanStore.use().clan_store.emit("is_ready")
def _populate_vms(self, done: Callable[[], None]) -> None:
# Execute `clan flakes add <path>` to democlan for this to work
# TODO: Make list_history a generator function
for entry in list_history():
GLib.idle_add(ClanStore.use().create_vm_task, entry)
GLib.idle_add(done)
def kill_vms(self) -> None:
log.debug("Killing all VMs")
ClanStore.use().kill_all()