aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2026-03-23 20:52:55 +0100
committerRubén Beltrán del Río <jj@r.bdr.sh>2026-03-26 18:07:20 +0100
commit8ce55652306908bf24ee351caad60bd21b0bef72 (patch)
treec4710c2be73bcfc5afb172dd76284735ee6924f0 /src
parent34a4ddeb3ad4c2b4e317648f58db5a460f4a14bb (diff)
Use breakpoints to disable horizontal layout at smaller sizes
Diffstat (limited to 'src')
-rw-r--r--src/actions.rs4
-rw-r--r--src/dialogs.rs12
-rw-r--r--src/file_registry.rs8
-rw-r--r--src/main.rs246
-rw-r--r--src/preferences/pages/general.rs6
-rw-r--r--src/preferences/window.rs6
6 files changed, 159 insertions, 123 deletions
diff --git a/src/actions.rs b/src/actions.rs
index fdea125..1d60820 100644
--- a/src/actions.rs
+++ b/src/actions.rs
@@ -60,6 +60,10 @@ pub enum Action {
MarkInitialized,
CloseWindow,
+ // Layout Transition
+ DisableHorizontalLayout,
+ EnableHorizontalLayout,
+
// Preferences
ShowPreferences,
PreferencesChanged(UserPreferences),
diff --git a/src/dialogs.rs b/src/dialogs.rs
index 242f3d6..22b12df 100644
--- a/src/dialogs.rs
+++ b/src/dialogs.rs
@@ -20,7 +20,7 @@ use std::path::PathBuf;
use gtk::prelude::*;
use relm4::gtk::gio;
-use relm4::{ComponentSender, gtk};
+use relm4::{ComponentSender, adw, gtk};
use crate::AppModel;
use crate::actions::{Action, ImageFormat};
@@ -38,7 +38,7 @@ pub fn create_file_filter() -> gtk::FileFilter {
}
/// Shows an error dialog with the given title and message.
-pub fn show_error(window: &gtk::Window, title: &str, message: &str) {
+pub fn show_error(window: &adw::Window, title: &str, message: &str) {
let dialog = gtk::AlertDialog::builder()
.modal(true)
.buttons([&tr!("dialog.action.cancel"), "Ok"])
@@ -50,7 +50,7 @@ pub fn show_error(window: &gtk::Window, title: &str, message: &str) {
/// Shows a file open dialog and handles the response.
pub fn show_open_dialog(
- window: &gtk::Window,
+ window: &adw::Window,
is_empty_document: bool,
sender: ComponentSender<AppModel>,
) {
@@ -86,7 +86,7 @@ pub fn show_open_dialog(
/// Shows a file save dialog and handles the response.
pub fn show_save_dialog(
- window: &gtk::Window,
+ window: &adw::Window,
current_file: Option<&PathBuf>,
close_after: bool,
sender: ComponentSender<AppModel>,
@@ -127,7 +127,7 @@ pub fn show_save_dialog(
/// Shows a dialog asking whether to save unsaved changes before closing.
pub fn show_unsaved_changes_dialog(
- window: &gtk::Window,
+ window: &adw::Window,
current_file: Option<PathBuf>,
sender: ComponentSender<AppModel>,
) {
@@ -184,7 +184,7 @@ fn create_image_filter(format: ImageFormat) -> gtk::FileFilter {
/// Shows an export image dialog and handles the response.
pub fn show_export_dialog(
- window: &gtk::Window,
+ window: &adw::Window,
current_file: Option<&PathBuf>,
sender: ComponentSender<AppModel>,
) {
diff --git a/src/file_registry.rs b/src/file_registry.rs
index 749697d..188e1fe 100644
--- a/src/file_registry.rs
+++ b/src/file_registry.rs
@@ -21,7 +21,7 @@ use std::cell::RefCell;
use std::path::PathBuf;
use relm4::component::Controller;
-use relm4::gtk;
+use relm4::{adw};
use crate::AppModel;
use crate::actions::Action;
@@ -31,7 +31,7 @@ thread_local! {
static WINDOW_CONTROLLERS: RefCell<Vec<Controller<AppModel>>> = const { RefCell::new(Vec::new()) };
/// Tracks which files are currently open to prevent duplicate windows.
- static OPEN_FILES: RefCell<Vec<(PathBuf, gtk::Window)>> = const { RefCell::new(Vec::new()) };
+ static OPEN_FILES: RefCell<Vec<(PathBuf, adw::Window)>> = const { RefCell::new(Vec::new()) };
/// Stores senders for all windows (including main window) to enable broadcasting.
static WINDOW_SENDERS: RefCell<Vec<relm4::Sender<Action>>> = const { RefCell::new(Vec::new()) };
@@ -45,7 +45,7 @@ pub fn store_controller(controller: Controller<AppModel>) {
}
/// Returns the window that has the given file open, if any.
-pub fn find_window_for_file(path: &PathBuf) -> Option<gtk::Window> {
+pub fn find_window_for_file(path: &PathBuf) -> Option<adw::Window> {
OPEN_FILES.with_borrow(|files| {
files
.iter()
@@ -55,7 +55,7 @@ pub fn find_window_for_file(path: &PathBuf) -> Option<gtk::Window> {
}
/// Registers a file as being open in the given window.
-pub fn register_file(path: &PathBuf, window: &gtk::Window) {
+pub fn register_file(path: &PathBuf, window: &adw::Window) {
OPEN_FILES.with_borrow_mut(|files| {
if !files.iter().any(|(file_path, _)| file_path == path) {
files.push((path.clone(), window.clone()));
diff --git a/src/main.rs b/src/main.rs
index 41d3f68..e1d3bc9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -47,8 +47,9 @@ use relm4::{
Component, ComponentController, ComponentParts, ComponentSender, RelmApp, RelmWidgetExt,
SimpleComponent,
actions::{AccelsPlus, ActionablePlus, RelmAction, RelmActionGroup},
- gtk,
+ gtk, adw
};
+use adw::prelude::*;
use sourceview5::LanguageManager;
use sourceview5::prelude::*;
use stages::{ALL_STAGE_TYPES, LocalizedStageType};
@@ -88,105 +89,12 @@ macro_rules! register_action {
let action: RelmAction<$action_type> = RelmAction::new_stateless(move |_| {
sender.input($message);
});
- $group.add_action(action);
+ $group.add_action(action.clone());
$app.set_accelerators_for_action::<$action_type>($accelerators);
+ action
}};
}
-fn setup_actions(window: &gtk::Window, sender: &ComponentSender<AppModel>) {
- let application = relm4::main_application();
- let mut action_group = RelmActionGroup::<WindowActionGroup>::new();
-
- register_action!(
- action_group,
- application,
- sender,
- ZoomInAction,
- Action::ZoomIn,
- &["<Primary>equal", "<Primary>plus"]
- );
- register_action!(
- action_group,
- application,
- sender,
- ZoomOutAction,
- Action::ZoomOut,
- &["<Primary>minus"]
- );
- register_action!(
- action_group,
- application,
- sender,
- ChangeOrientationAction,
- Action::ChangeOrientation,
- &["<Primary>l"]
- );
- register_action!(
- action_group,
- application,
- sender,
- ExportImageAction,
- Action::ExportImage,
- &["<Primary>e"]
- );
- register_action!(
- action_group,
- application,
- sender,
- NewAction,
- Action::New,
- &["<Primary>n"]
- );
- register_action!(
- action_group,
- application,
- sender,
- SaveAction,
- Action::Save { close_after: false },
- &["<Primary>s"]
- );
- register_action!(
- action_group,
- application,
- sender,
- SaveAsAction,
- Action::SaveAs { close_after: false },
- &["<Primary><Shift>s"]
- );
- register_action!(
- action_group,
- application,
- sender,
- OpenAction,
- Action::Open,
- &["<Primary>o"]
- );
- register_action!(
- action_group,
- application,
- sender,
- CloseAction,
- Action::CloseWindow,
- &["<Primary>w"]
- );
- register_action!(
- action_group,
- application,
- sender,
- PreferencesAction,
- Action::ShowPreferences,
- &["<Primary>comma"]
- );
-
- let sender = sender.clone();
- let action: RelmAction<NewFromTemplateAction> =
- RelmAction::new_with_target_value(move |_, template_id| {
- sender.input(Action::NewFromTemplateById(template_id));
- });
- action_group.add_action(action);
- action_group.register_for_widget(window);
-}
-
struct AppInit {
zoom: f64,
file_path: Option<PathBuf>,
@@ -211,12 +119,14 @@ struct AppModel {
modified: bool,
pending_load_events: RefCell<u32>,
initialized: bool,
- window: gtk::Window,
+ window: adw::Window,
preferences: preferences::UserPreferences,
preferences_window: Option<relm4::Controller<preferences::PreferencesWindow>>,
editor_css_provider: gtk::CssProvider,
templates_menu: gio::Menu,
main_menu: gio::Menu,
+ horizontal_layout_enabled: bool,
+ layout_action: RelmAction<ChangeOrientationAction>
}
impl AppModel {
@@ -487,7 +397,7 @@ impl SimpleComponent for AppModel {
view! {
#[root]
- gtk::Window {
+ adw::Window {
#[watch]
set_title: Some(&model.window_title()),
set_default_width: constants::DEFAULT_WINDOW_WIDTH,
@@ -497,10 +407,8 @@ impl SimpleComponent for AppModel {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 0,
- gtk::HeaderBar {
- pack_start = &gtk::Box {
- set_orientation: gtk::Orientation::Horizontal,
-
+ adw::HeaderBar {
+ pack_start = &adw::ToolbarView {
gtk::Button::with_label(&match model.orientation {
gtk::Orientation::Horizontal => tr!("command.view.use_vertical_layout"),
_ => tr!("command.view.use_horizontal_layout")
@@ -510,11 +418,12 @@ impl SimpleComponent for AppModel {
gtk::Orientation::Horizontal => SPLIT_VERTICAL_REGULAR,
_ => SPLIT_HORIZONTAL_REGULAR
},
+ #[watch]
+ set_visible: model.horizontal_layout_enabled,
ActionablePlus::set_stateless_action::<ChangeOrientationAction>: &(),
}
},
- pack_end = &gtk::Box {
- set_orientation: gtk::Orientation::Horizontal,
+ pack_end = &adw::ToolbarView {
gtk::Button::with_label(&tr!("command.file.export")) {
set_icon_name: IMAGE_REGULAR,
@@ -540,7 +449,7 @@ impl SimpleComponent for AppModel {
gtk::Paned {
#[watch]
- set_orientation: model.orientation,
+ set_orientation: if model.horizontal_layout_enabled { model.orientation } else { gtk::Orientation::Vertical },
set_expand: true,
#[wrap(Some)]
@@ -623,8 +532,6 @@ impl SimpleComponent for AppModel {
let (source, initial_content, current_file) = Self::init_source_buffer(&init, &sender);
- setup_actions(&root, &sender);
-
let source_view = sourceview5::View::with_buffer(&source);
source_view.set_monospace(true);
@@ -648,6 +555,100 @@ impl SimpleComponent for AppModel {
let prefs = preferences::storage::load();
let (templates_menu, main_menu) = Self::init_main_menu(&prefs);
+ // Actions
+
+ let application = relm4::main_application();
+ let mut action_group = RelmActionGroup::<WindowActionGroup>::new();
+
+ register_action!(
+ action_group,
+ application,
+ sender,
+ ZoomInAction,
+ Action::ZoomIn,
+ &["<Primary>equal", "<Primary>plus"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ ZoomOutAction,
+ Action::ZoomOut,
+ &["<Primary>minus"]
+ );
+ let layout_action = register_action!(
+ action_group,
+ application,
+ sender,
+ ChangeOrientationAction,
+ Action::ChangeOrientation,
+ &["<Primary>l"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ ExportImageAction,
+ Action::ExportImage,
+ &["<Primary>e"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ NewAction,
+ Action::New,
+ &["<Primary>n"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ SaveAction,
+ Action::Save { close_after: false },
+ &["<Primary>s"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ SaveAsAction,
+ Action::SaveAs { close_after: false },
+ &["<Primary><Shift>s"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ OpenAction,
+ Action::Open,
+ &["<Primary>o"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ CloseAction,
+ Action::CloseWindow,
+ &["<Primary>w"]
+ );
+ register_action!(
+ action_group,
+ application,
+ sender,
+ PreferencesAction,
+ Action::ShowPreferences,
+ &["<Primary>comma"]
+ );
+
+ let sender_clone = sender.clone();
+ let action: RelmAction<NewFromTemplateAction> =
+ RelmAction::new_with_target_value(move |_, template_id| {
+ sender_clone.input(Action::NewFromTemplateById(template_id));
+ });
+ action_group.add_action(action);
+ action_group.register_for_widget(&root);
+
let mut model = AppModel {
orientation: gtk::Orientation::Horizontal,
stage_type_list,
@@ -672,6 +673,8 @@ impl SimpleComponent for AppModel {
editor_css_provider,
templates_menu,
main_menu,
+ horizontal_layout_enabled: true,
+ layout_action
};
model.apply_preferences();
@@ -697,6 +700,23 @@ impl SimpleComponent for AppModel {
let widgets = view_output!();
+ // Breakpoints for Smaller Layouts
+ let breakpoint = adw::Breakpoint::new(
+ adw::BreakpointCondition::new_length(
+ adw::BreakpointConditionLengthType::MinWidth,
+ 440.0,
+ adw::LengthUnit::Sp
+ )
+ );
+ let sender_clone = sender.clone();
+ breakpoint.connect_apply(move |_| {
+ sender_clone.input(Action::EnableHorizontalLayout);
+ });
+ let sender_clone = sender.clone();
+ breakpoint.connect_unapply(move |_| {
+ sender_clone.input(Action::DisableHorizontalLayout);
+ });
+ root.add_breakpoint(breakpoint);
ComponentParts { model, widgets }
}
@@ -764,6 +784,18 @@ impl SimpleComponent for AppModel {
Action::MarkInitialized => self.initialized = true,
+ // Layout Transition
+ Action::DisableHorizontalLayout => {
+ self.horizontal_layout_enabled = false;
+ self.layout_action.set_enabled(false);
+ },
+ Action::EnableHorizontalLayout => {
+ self.horizontal_layout_enabled = true;
+ self.layout_action.set_enabled(true);
+ },
+
+ // Preferences
+
Action::ShowPreferences => {
if self.preferences_window.is_none() {
let controller = preferences::PreferencesWindow::builder()
diff --git a/src/preferences/pages/general.rs b/src/preferences/pages/general.rs
index d1596c4..e65ede6 100644
--- a/src/preferences/pages/general.rs
+++ b/src/preferences/pages/general.rs
@@ -17,7 +17,7 @@
use gtk::FileFilter;
use gtk::prelude::*;
use relm4::Sender;
-use relm4::gtk;
+use relm4::{adw, gtk};
use crate::tr;
@@ -92,7 +92,7 @@ impl GeneralPage {
}
}
-pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: &gtk::Window) {
+pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: &adw::Window) {
let filter = FileFilter::new();
filter.add_pattern("*.json");
filter.set_name(Some(&tr!("dialog.file_type.json")));
@@ -118,7 +118,7 @@ pub fn show_export_dialog(sender: &Sender<PreferencesInput>, window: &gtk::Windo
});
}
-pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: &gtk::Window) {
+pub fn show_import_dialog(sender: &Sender<PreferencesInput>, window: &adw::Window) {
let filter = FileFilter::new();
filter.add_pattern("*.json");
filter.set_name(Some(&tr!("dialog.file_type.json")));
diff --git a/src/preferences/window.rs b/src/preferences/window.rs
index 0997716..b7db8c5 100644
--- a/src/preferences/window.rs
+++ b/src/preferences/window.rs
@@ -20,7 +20,7 @@
use std::path::PathBuf;
use gtk::prelude::*;
-use relm4::{ComponentParts, ComponentSender, SimpleComponent, gtk};
+use relm4::{ComponentParts, ComponentSender, SimpleComponent, adw, gtk};
use crate::tr;
@@ -85,7 +85,7 @@ pub enum PreferencesOutput {
pub struct PreferencesWindow {
preferences: UserPreferences,
- window: gtk::Window,
+ window: adw::Window,
// Widget references for pages
general_page: general::GeneralPage,
editor_page: editor::EditorPage,
@@ -101,7 +101,7 @@ impl SimpleComponent for PreferencesWindow {
type Output = PreferencesOutput;
view! {
- gtk::Window {
+ adw::Window {
set_title: Some(&tr!("preferences.title")),
set_default_width: 700,
set_default_height: 500,