Add menu and fix hrop hint
This commit is contained in:
parent
36bf30e314
commit
b71db3e429
7 changed files with 144 additions and 50 deletions
|
@ -10,15 +10,19 @@ from importlib.resources import files
|
||||||
|
|
||||||
Adw.init()
|
Adw.init()
|
||||||
|
|
||||||
|
|
||||||
def load_resources():
|
def load_resources():
|
||||||
resource_path = files("recoder").joinpath("resources.gresource")
|
resource_path = files("recoder").joinpath("resources.gresource")
|
||||||
resource = Gio.Resource.load(str(resource_path))
|
resource = Gio.Resource.load(str(resource_path))
|
||||||
Gio.resources_register(resource)
|
Gio.resources_register(resource)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
load_resources()
|
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):
|
class RecoderApp(Adw.Application):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -27,14 +31,50 @@ def main():
|
||||||
flags=Gio.ApplicationFlags.FLAGS_NONE
|
flags=Gio.ApplicationFlags.FLAGS_NONE
|
||||||
)
|
)
|
||||||
self.window = 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):
|
def do_activate(self):
|
||||||
if not self.window:
|
if not self.window:
|
||||||
self.window = RecoderWindow(self)
|
self.window = RecoderWindow(self)
|
||||||
|
self.window.connect("close-request", self.on_window_close)
|
||||||
self.window.present()
|
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()
|
app = RecoderApp()
|
||||||
return app.run(sys.argv)
|
return app.run(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
|
|
@ -63,7 +63,7 @@ class UIStateManager:
|
||||||
if w.drop_hint.get_parent() != w.overlay:
|
if w.drop_hint.get_parent() != w.overlay:
|
||||||
w.overlay.add_overlay(w.drop_hint)
|
w.overlay.add_overlay(w.drop_hint)
|
||||||
w.btn_transcode.set_visible(False)
|
w.btn_transcode.set_visible(False)
|
||||||
w.btn_cancel.set_visible(False)
|
w.btn_clear.set_visible(False)
|
||||||
|
|
||||||
def _handle_files_loaded(self):
|
def _handle_files_loaded(self):
|
||||||
w = self.window
|
w = self.window
|
||||||
|
@ -74,7 +74,7 @@ class UIStateManager:
|
||||||
w.btn_transcode.set_sensitive(True)
|
w.btn_transcode.set_sensitive(True)
|
||||||
w.btn_transcode.set_label("Transcode")
|
w.btn_transcode.set_label("Transcode")
|
||||||
w.btn_transcode.add_css_class("suggested-action")
|
w.btn_transcode.add_css_class("suggested-action")
|
||||||
w.btn_cancel.set_visible(True)
|
w.btn_clear.set_visible(True)
|
||||||
w.is_paused = False
|
w.is_paused = False
|
||||||
|
|
||||||
def _handle_transcoding(self):
|
def _handle_transcoding(self):
|
||||||
|
@ -85,7 +85,7 @@ class UIStateManager:
|
||||||
w.btn_transcode.set_sensitive(True)
|
w.btn_transcode.set_sensitive(True)
|
||||||
w.btn_transcode.set_label("Pause")
|
w.btn_transcode.set_label("Pause")
|
||||||
w.btn_transcode.remove_css_class("suggested-action")
|
w.btn_transcode.remove_css_class("suggested-action")
|
||||||
w.btn_cancel.set_visible(True)
|
w.btn_clear.set_visible(True)
|
||||||
w.is_paused = False
|
w.is_paused = False
|
||||||
|
|
||||||
def _handle_paused(self):
|
def _handle_paused(self):
|
||||||
|
@ -95,7 +95,7 @@ class UIStateManager:
|
||||||
w.btn_transcode.set_visible(True)
|
w.btn_transcode.set_visible(True)
|
||||||
w.btn_transcode.set_sensitive(True)
|
w.btn_transcode.set_sensitive(True)
|
||||||
w.btn_transcode.set_label("Resume")
|
w.btn_transcode.set_label("Resume")
|
||||||
w.btn_cancel.set_visible(True)
|
w.btn_clear.set_visible(True)
|
||||||
w.is_paused = True
|
w.is_paused = True
|
||||||
|
|
||||||
def _handle_done(self):
|
def _handle_done(self):
|
||||||
|
@ -105,7 +105,7 @@ class UIStateManager:
|
||||||
w.progress_bar.set_fraction(1.0)
|
w.progress_bar.set_fraction(1.0)
|
||||||
w.btn_transcode.set_visible(False)
|
w.btn_transcode.set_visible(False)
|
||||||
w.btn_transcode.remove_css_class("suggested-action")
|
w.btn_transcode.remove_css_class("suggested-action")
|
||||||
w.btn_cancel.set_visible(True)
|
w.btn_clear.set_visible(True)
|
||||||
w.is_paused = False
|
w.is_paused = False
|
||||||
|
|
||||||
def _handle_error(self):
|
def _handle_error(self):
|
||||||
|
@ -114,5 +114,5 @@ class UIStateManager:
|
||||||
w.drop_hint.set_visible(False)
|
w.drop_hint.set_visible(False)
|
||||||
w.progress_bar.set_visible(False)
|
w.progress_bar.set_visible(False)
|
||||||
w.btn_transcode.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
|
w.is_paused = False
|
||||||
|
|
|
@ -37,11 +37,11 @@ class DropHandler:
|
||||||
def on_drop_enter(self, *_):
|
def on_drop_enter(self, *_):
|
||||||
if not self._accepting:
|
if not self._accepting:
|
||||||
return False
|
return False
|
||||||
self.w.overlay.add_css_class("drop-highlight")
|
self.w.drop_hint.add_css_class("drop-highlight")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_drop_leave(self, *_):
|
def on_drop_leave(self, *_):
|
||||||
self.w.overlay.remove_css_class("drop-highlight")
|
self.w.drop_hint.remove_css_class("drop-highlight")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def on_drop(self, _, value, __, ___):
|
def on_drop(self, _, value, __, ___):
|
||||||
|
|
|
@ -7,7 +7,7 @@ from recoder.models import FileStatus
|
||||||
ICONS = {
|
ICONS = {
|
||||||
FileStatus.WAITING: "network-idle-symbolic",
|
FileStatus.WAITING: "network-idle-symbolic",
|
||||||
FileStatus.PROCESSING: "network-transmit-symbolic",
|
FileStatus.PROCESSING: "network-transmit-symbolic",
|
||||||
FileStatus.DONE: "check-plain-symbolic",
|
FileStatus.DONE: "checkmark-symbolic",
|
||||||
FileStatus.ERROR: "network-error-symbolic",
|
FileStatus.ERROR: "network-error-symbolic",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ class FileEntryRow(Gtk.ListBoxRow):
|
||||||
def update_display(self, *args):
|
def update_display(self, *args):
|
||||||
basename = self.item.file.get_basename()
|
basename = self.item.file.get_basename()
|
||||||
self.label.set_text(basename)
|
self.label.set_text(basename)
|
||||||
|
|
||||||
icon_name = ICONS.get(self.item.status, "object-select-symbolic")
|
icon_name = ICONS.get(self.item.status, "object-select-symbolic")
|
||||||
self.icon.set_from_icon_name(icon_name)
|
self.icon.set_from_icon_name(icon_name)
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,9 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
listbox = Gtk.Template.Child()
|
listbox = Gtk.Template.Child()
|
||||||
scrolled_window = Gtk.Template.Child()
|
scrolled_window = Gtk.Template.Child()
|
||||||
btn_transcode = Gtk.Template.Child()
|
btn_transcode = Gtk.Template.Child()
|
||||||
btn_cancel = Gtk.Template.Child()
|
btn_clear = Gtk.Template.Child()
|
||||||
progress_bar = Gtk.Template.Child()
|
progress_bar = Gtk.Template.Child()
|
||||||
folder_label = Gtk.Template.Child()
|
folder_label = Gtk.Template.Child()
|
||||||
btn_preferences = Gtk.Template.Child()
|
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self, application):
|
||||||
super().__init__(application=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-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT)
|
||||||
self.state_settings.bind("is-fullscreen", self, "fullscreened", 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.file_items_to_process = []
|
||||||
self.current_folder_name = None
|
self.current_folder_name = None
|
||||||
self.transcoder = None
|
self.transcoder = None
|
||||||
|
@ -53,8 +56,7 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
self.ui_manager = UIStateManager(self, self.app_state_manager)
|
self.ui_manager = UIStateManager(self, self.app_state_manager)
|
||||||
|
|
||||||
self.btn_transcode.connect("clicked", self.on_transcode_clicked)
|
self.btn_transcode.connect("clicked", self.on_transcode_clicked)
|
||||||
self.btn_cancel.connect("clicked", self.on_cancel_clicked)
|
self.btn_clear.connect("clicked", self.on_clear_clicked)
|
||||||
self.btn_preferences.connect("clicked", self.on_preferences_clicked)
|
|
||||||
|
|
||||||
self.app_state_manager.state = AppState.IDLE
|
self.app_state_manager.state = AppState.IDLE
|
||||||
|
|
||||||
|
@ -68,22 +70,6 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
|
|
||||||
Notify.init("Recoder")
|
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):
|
def process_drop_value(self, value):
|
||||||
folder_file = None
|
folder_file = None
|
||||||
|
@ -150,7 +136,7 @@ class RecoderWindow(Adw.ApplicationWindow):
|
||||||
self.is_paused = False
|
self.is_paused = False
|
||||||
self.app_state_manager.state = AppState.TRANSCODING
|
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:
|
if self.transcoder and self.transcoder.is_processing:
|
||||||
self.transcoder.stop()
|
self.transcoder.stop()
|
||||||
self.transcoder = None
|
self.transcoder = None
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
.drop-highlight {
|
.drop-highlight {
|
||||||
background-color: @theme_selected_bg_color; /* good: uses theme color */
|
background: alpha(@accent_color, 0.12);
|
||||||
border: 2px solid @theme_selected_fg_color; /* good contrast */
|
border: 2px solid alpha(@accent_color, 0.9);
|
||||||
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 */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dim-label {
|
.dim-label {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: @theme_unfocused_fg_color;
|
color: @theme_unfocused_fg_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dim-icon {
|
||||||
|
opacity: 0.3;
|
||||||
|
color: @theme_unfocused_fg_color;
|
||||||
|
}
|
|
@ -3,6 +3,17 @@
|
||||||
<requires lib="gtk" version="4.0"/>
|
<requires lib="gtk" version="4.0"/>
|
||||||
<requires lib="adw" version="1.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">
|
<template class="RecoderWindow" parent="AdwApplicationWindow">
|
||||||
<property name="title">Recoder</property>
|
<property name="title">Recoder</property>
|
||||||
<property name="default-width">700</property>
|
<property name="default-width">700</property>
|
||||||
|
@ -26,15 +37,21 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child type="end">
|
<child type="end">
|
||||||
<object class="GtkButton" id="btn_preferences">
|
<object class="GtkMenuButton" id="menu_button">
|
||||||
<property name="icon-name">emblem-system-symbolic</property>
|
<property name="icon-name">open-menu-symbolic</property>
|
||||||
<property name="tooltip-text">Preferences</property>
|
<property name="menu-model">main_menu</property>
|
||||||
|
<property name="tooltip-text">Menu</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child type="end">
|
<child type="end">
|
||||||
<object class="GtkButton" id="btn_cancel">
|
<object class="GtkButton" id="btn_clear">
|
||||||
<property name="label">Cancel</property>
|
<property name="icon-name">edit-clear-symbolic</property>
|
||||||
|
<property name="tooltip-text">Clear</property>
|
||||||
<property name="visible">False</property>
|
<property name="visible">False</property>
|
||||||
|
<property name="can-focus">False</property>
|
||||||
|
<style>
|
||||||
|
<class name="flat"/>
|
||||||
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -70,10 +87,55 @@
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
<child type="overlay">
|
<child type="overlay">
|
||||||
<object class="GtkLabel" id="drop_hint">
|
<object class="GtkBox" id="drop_hint">
|
||||||
<property name="label">📂 Drop files or folders here to get started</property>
|
<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="halign">center</property>
|
||||||
<property name="valign">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>
|
<style>
|
||||||
<class name="dim-label"/>
|
<class name="dim-label"/>
|
||||||
</style>
|
</style>
|
||||||
|
@ -81,6 +143,11 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkProgressBar" id="progress_bar">
|
<object class="GtkProgressBar" id="progress_bar">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue