Add menu and fix hrop hint

This commit is contained in:
Jeena 2025-06-06 16:47:26 +09:00
parent 36bf30e314
commit b71db3e429
7 changed files with 144 additions and 50 deletions

View file

@ -10,15 +10,19 @@ from importlib.resources import files
Adw.init()
def load_resources():
resource_path = files("recoder").joinpath("resources.gresource")
resource = Gio.Resource.load(str(resource_path))
Gio.resources_register(resource)
def main():
load_resources()
from recoder.window import RecoderWindow # delayed import
# Delay imports until after resources are registered
from recoder.window import RecoderWindow
from recoder.preferences import RecoderPreferences
class RecoderApp(Adw.Application):
def __init__(self):
@ -27,14 +31,50 @@ def main():
flags=Gio.ApplicationFlags.FLAGS_NONE
)
self.window = None
self.preferences_window = None
def do_startup(self):
Adw.Application.do_startup(self)
quit_action = Gio.SimpleAction.new("quit", None)
quit_action.connect("activate", lambda *_: self.quit())
self.add_action(quit_action)
self.set_accels_for_action("app.quit", ["<Ctrl>q"])
self.set_accels_for_action("win.close", ["<Ctrl>w"])
preferences_action = Gio.SimpleAction.new("preferences", None)
preferences_action.connect("activate", self.on_preferences_activate)
self.add_action(preferences_action)
# Add the accelerator for Preferences (Ctrl+,)
self.set_accels_for_action("app.preferences", ["<Primary>comma"])
def do_activate(self):
if not self.window:
self.window = RecoderWindow(self)
self.window.connect("close-request", self.on_window_close)
self.window.present()
def on_preferences_activate(self, action, param):
if not self.preferences_window:
self.preferences_window = RecoderPreferences()
self.preferences_window.set_transient_for(self.window)
self.preferences_window.set_modal(True)
self.preferences_window.connect("close-request", self.on_preferences_close)
self.preferences_window.present()
def on_preferences_close(self, window):
window.set_visible(False)
# Don't destroy, just hide
return True # stops further handlers, prevents default destruction
def on_window_close(self, window):
self.quit()
return False # allow default handler to proceed
app = RecoderApp()
return app.run(sys.argv)
if __name__ == "__main__":
sys.exit(main())

View file

@ -63,7 +63,7 @@ class UIStateManager:
if w.drop_hint.get_parent() != w.overlay:
w.overlay.add_overlay(w.drop_hint)
w.btn_transcode.set_visible(False)
w.btn_cancel.set_visible(False)
w.btn_clear.set_visible(False)
def _handle_files_loaded(self):
w = self.window
@ -74,7 +74,7 @@ class UIStateManager:
w.btn_transcode.set_sensitive(True)
w.btn_transcode.set_label("Transcode")
w.btn_transcode.add_css_class("suggested-action")
w.btn_cancel.set_visible(True)
w.btn_clear.set_visible(True)
w.is_paused = False
def _handle_transcoding(self):
@ -85,7 +85,7 @@ class UIStateManager:
w.btn_transcode.set_sensitive(True)
w.btn_transcode.set_label("Pause")
w.btn_transcode.remove_css_class("suggested-action")
w.btn_cancel.set_visible(True)
w.btn_clear.set_visible(True)
w.is_paused = False
def _handle_paused(self):
@ -95,7 +95,7 @@ class UIStateManager:
w.btn_transcode.set_visible(True)
w.btn_transcode.set_sensitive(True)
w.btn_transcode.set_label("Resume")
w.btn_cancel.set_visible(True)
w.btn_clear.set_visible(True)
w.is_paused = True
def _handle_done(self):
@ -105,7 +105,7 @@ class UIStateManager:
w.progress_bar.set_fraction(1.0)
w.btn_transcode.set_visible(False)
w.btn_transcode.remove_css_class("suggested-action")
w.btn_cancel.set_visible(True)
w.btn_clear.set_visible(True)
w.is_paused = False
def _handle_error(self):
@ -114,5 +114,5 @@ class UIStateManager:
w.drop_hint.set_visible(False)
w.progress_bar.set_visible(False)
w.btn_transcode.set_visible(False)
w.btn_cancel.set_visible(True)
w.btn_clear.set_visible(True)
w.is_paused = False

View file

@ -37,11 +37,11 @@ class DropHandler:
def on_drop_enter(self, *_):
if not self._accepting:
return False
self.w.overlay.add_css_class("drop-highlight")
self.w.drop_hint.add_css_class("drop-highlight")
return True
def on_drop_leave(self, *_):
self.w.overlay.remove_css_class("drop-highlight")
self.w.drop_hint.remove_css_class("drop-highlight")
return True
def on_drop(self, _, value, __, ___):

View file

@ -7,7 +7,7 @@ from recoder.models import FileStatus
ICONS = {
FileStatus.WAITING: "network-idle-symbolic",
FileStatus.PROCESSING: "network-transmit-symbolic",
FileStatus.DONE: "check-plain-symbolic",
FileStatus.DONE: "checkmark-symbolic",
FileStatus.ERROR: "network-error-symbolic",
}
@ -36,7 +36,6 @@ class FileEntryRow(Gtk.ListBoxRow):
def update_display(self, *args):
basename = self.item.file.get_basename()
self.label.set_text(basename)
icon_name = ICONS.get(self.item.status, "object-select-symbolic")
self.icon.set_from_icon_name(icon_name)

View file

@ -26,10 +26,9 @@ class RecoderWindow(Adw.ApplicationWindow):
listbox = Gtk.Template.Child()
scrolled_window = Gtk.Template.Child()
btn_transcode = Gtk.Template.Child()
btn_cancel = Gtk.Template.Child()
btn_clear = Gtk.Template.Child()
progress_bar = Gtk.Template.Child()
folder_label = Gtk.Template.Child()
btn_preferences = Gtk.Template.Child()
def __init__(self, application):
super().__init__(application=application)
@ -42,6 +41,10 @@ class RecoderWindow(Adw.ApplicationWindow):
self.state_settings.bind("is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT)
self.state_settings.bind("is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT)
close_action = Gio.SimpleAction.new("close", None)
close_action.connect("activate", lambda *a: self.close())
self.add_action(close_action)
self.file_items_to_process = []
self.current_folder_name = None
self.transcoder = None
@ -53,8 +56,7 @@ class RecoderWindow(Adw.ApplicationWindow):
self.ui_manager = UIStateManager(self, self.app_state_manager)
self.btn_transcode.connect("clicked", self.on_transcode_clicked)
self.btn_cancel.connect("clicked", self.on_cancel_clicked)
self.btn_preferences.connect("clicked", self.on_preferences_clicked)
self.btn_clear.connect("clicked", self.on_clear_clicked)
self.app_state_manager.state = AppState.IDLE
@ -68,22 +70,6 @@ class RecoderWindow(Adw.ApplicationWindow):
Notify.init("Recoder")
self.preferences_window = None
def on_preferences_clicked(self, button):
if self.preferences_window is None:
self.preferences_window = RecoderPreferences()
self.preferences_window.set_transient_for(self)
self.preferences_window.set_modal(True)
self.preferences_window.connect("close-request", self.on_preferences_window_close)
self.preferences_window.present()
def on_preferences_window_close(self, window):
window.hide()
# Don't destroy, just hide
return True # stops further handlers, prevents default destruction
def process_drop_value(self, value):
folder_file = None
@ -150,7 +136,7 @@ class RecoderWindow(Adw.ApplicationWindow):
self.is_paused = False
self.app_state_manager.state = AppState.TRANSCODING
def on_cancel_clicked(self, button):
def on_clear_clicked(self, button):
if self.transcoder and self.transcoder.is_processing:
self.transcoder.stop()
self.transcoder = None

View file

@ -1,12 +1,14 @@
.drop-highlight {
background-color: @theme_selected_bg_color; /* good: uses theme color */
border: 2px solid @theme_selected_fg_color; /* good contrast */
border-radius: 6px; /* soften edges */
box-shadow: 0 0 8px @theme_selected_bg_color; /* subtle glow */
transition: background-color 150ms ease, box-shadow 150ms ease; /* smooth */
background: alpha(@accent_color, 0.12);
border: 2px solid alpha(@accent_color, 0.9);
}
.dim-label {
font-size: 16px;
color: @theme_unfocused_fg_color;
}
.dim-icon {
opacity: 0.3;
color: @theme_unfocused_fg_color;
}

View file

@ -3,6 +3,17 @@
<requires lib="gtk" version="4.0"/>
<requires lib="adw" version="1.0"/>
<menu id="main_menu">
<item>
<attribute name="label">Preferences</attribute>
<attribute name="action">app.preferences</attribute>
</item>
<item>
<attribute name="label">Quit</attribute>
<attribute name="action">app.quit</attribute>
</item>
</menu>
<template class="RecoderWindow" parent="AdwApplicationWindow">
<property name="title">Recoder</property>
<property name="default-width">700</property>
@ -26,15 +37,21 @@
</object>
</child>
<child type="end">
<object class="GtkButton" id="btn_preferences">
<property name="icon-name">emblem-system-symbolic</property>
<property name="tooltip-text">Preferences</property>
<object class="GtkMenuButton" id="menu_button">
<property name="icon-name">open-menu-symbolic</property>
<property name="menu-model">main_menu</property>
<property name="tooltip-text">Menu</property>
</object>
</child>
</child>
<child type="end">
<object class="GtkButton" id="btn_cancel">
<property name="label">Cancel</property>
<object class="GtkButton" id="btn_clear">
<property name="icon-name">edit-clear-symbolic</property>
<property name="tooltip-text">Clear</property>
<property name="visible">False</property>
<property name="can-focus">False</property>
<style>
<class name="flat"/>
</style>
</object>
</child>
</object>
@ -70,15 +87,65 @@
</child>
<child type="overlay">
<object class="GtkLabel" id="drop_hint">
<property name="label">📂 Drop files or folders here to get started</property>
<property name="halign">center</property>
<property name="valign">center</property>
<style>
<class name="dim-label"/>
</style>
<object class="GtkBox" id="drop_hint">
<property name="orientation">vertical</property>
<property name="halign">fill</property>
<property name="valign">fill</property>
<property name="spacing">48</property>
<property name="hexpand">true</property>
<property name="vexpand">true</property>
<!-- Inner box to center content -->
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="vexpand">true</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="spacing">48</property>
<child>
<object class="GtkBox">
<property name="orientation">horizontal</property>
<property name="halign">center</property>
<property name="spacing">48</property>
<child>
<object class="GtkImage">
<property name="icon-name">video-x-generic-symbolic</property>
<property name="pixel-size">48</property>
<style>
<class name="dim-icon"/>
</style>
</object>
</child>
<child>
<object class="GtkImage">
<property name="icon-name">folder-symbolic</property>
<property name="pixel-size">48</property>
<style>
<class name="dim-icon"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="label">Drop video files or folders here to get started</property>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</child>