feat(ui): add header toolbar with navigation and transform buttons
- Add header bar with nav toggle, prev/next, rotate and flip buttons - Extract header rendering to view/header.rs (MVU architecture) - Add RotateCW, RotateCCW, FlipHorizontal, FlipVertical messages - Add PrevDocument, NextDocument navigation messages - Persist nav_bar_visible and context_drawer_visible in config - Update properties panel with document info display"
This commit is contained in:
parent
b1b0999ebe
commit
7b36ff143c
8 changed files with 232 additions and 90 deletions
|
|
@ -40,28 +40,27 @@ error-unsupported-format = Unsupported file format.
|
||||||
|
|
||||||
## Properties panel
|
## Properties panel
|
||||||
panel-properties = Properties
|
panel-properties = Properties
|
||||||
meta-dimensions = Dimensions
|
meta-section-file = File Information
|
||||||
|
meta-section-exif = Camera Information
|
||||||
|
|
||||||
|
## Basic metadata
|
||||||
|
meta-filename = Name
|
||||||
meta-format = Format
|
meta-format = Format
|
||||||
|
meta-dimensions = Dimensions
|
||||||
|
meta-filesize = Size
|
||||||
|
meta-colortype = Color Type
|
||||||
|
meta-path = Path
|
||||||
meta-pages = Pages
|
meta-pages = Pages
|
||||||
meta-current-page = Current Page
|
meta-current-page = Current Page
|
||||||
|
|
||||||
## Metadata panel (extended)
|
## EXIF metadata
|
||||||
metadata = Metadata
|
meta-camera = Camera
|
||||||
file-name = File
|
meta-datetime = Date Taken
|
||||||
format = Format
|
meta-exposure = Exposure
|
||||||
resolution = Resolution
|
meta-aperture = Aperture
|
||||||
file-size = Size
|
meta-iso = ISO
|
||||||
color-type = Color
|
meta-focal = Focal Length
|
||||||
|
meta-gps = GPS Location
|
||||||
## EXIF data
|
|
||||||
exif-data = EXIF Data
|
|
||||||
camera = Camera
|
|
||||||
date-taken = Date
|
|
||||||
exposure = Exposure
|
|
||||||
aperture = Aperture
|
|
||||||
iso = ISO
|
|
||||||
focal-length = Focal
|
|
||||||
gps = GPS
|
|
||||||
|
|
||||||
## States
|
## States
|
||||||
loading-metadata = Loading...
|
loading-metadata = Loading...
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ pub enum AppMessage {
|
||||||
// === Panels (COSMIC-managed) ===
|
// === Panels (COSMIC-managed) ===
|
||||||
/// Toggle a context drawer page.
|
/// Toggle a context drawer page.
|
||||||
ToggleContextPage(ContextPage),
|
ToggleContextPage(ContextPage),
|
||||||
|
/// Toggle the nav bar (left panel) visibility.
|
||||||
|
ToggleNavBar,
|
||||||
|
|
||||||
// === Metadata ===
|
// === Metadata ===
|
||||||
/// Refresh metadata from the current document.
|
/// Refresh metadata from the current document.
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,11 @@ pub mod update;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
use cosmic::app::{context_drawer, Core};
|
use cosmic::app::{context_drawer, Core};
|
||||||
|
use cosmic::cosmic_config::{self, CosmicConfigEntry};
|
||||||
use cosmic::iced::keyboard::{self, key::Named, Key, Modifiers};
|
use cosmic::iced::keyboard::{self, key::Named, Key, Modifiers};
|
||||||
use cosmic::iced::window;
|
use cosmic::iced::window;
|
||||||
use cosmic::iced::Subscription;
|
use cosmic::iced::{Length, Subscription};
|
||||||
use cosmic::widget::{button, icon, nav_bar};
|
use cosmic::widget::{button, horizontal_space, icon, nav_bar};
|
||||||
use cosmic::{Action, Element, Task};
|
use cosmic::{Action, Element, Task};
|
||||||
|
|
||||||
pub use message::AppMessage;
|
pub use message::AppMessage;
|
||||||
|
|
@ -42,6 +43,8 @@ pub struct Noctua {
|
||||||
pub model: AppModel,
|
pub model: AppModel,
|
||||||
nav: nav_bar::Model,
|
nav: nav_bar::Model,
|
||||||
context_page: ContextPage,
|
context_page: ContextPage,
|
||||||
|
config: AppConfig,
|
||||||
|
config_handler: Option<cosmic_config::Config>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl cosmic::Application for Noctua {
|
impl cosmic::Application for Noctua {
|
||||||
|
|
@ -60,8 +63,17 @@ impl cosmic::Application for Noctua {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Action<Self::Message>>) {
|
fn init(mut core: Core, flags: Self::Flags) -> (Self, Task<Action<Self::Message>>) {
|
||||||
let config = AppConfig::default();
|
// Load persisted config.
|
||||||
let mut model = AppModel::new(config);
|
let (config, config_handler) =
|
||||||
|
match cosmic_config::Config::new(Self::APP_ID, AppConfig::VERSION) {
|
||||||
|
Ok(handler) => {
|
||||||
|
let config = AppConfig::get_entry(&handler).unwrap_or_default();
|
||||||
|
(config, Some(handler))
|
||||||
|
}
|
||||||
|
Err(_) => (AppConfig::default(), None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut model = AppModel::new(config.clone());
|
||||||
|
|
||||||
// Use CLI arguments from `flags` to open initial file or folder.
|
// Use CLI arguments from `flags` to open initial file or folder.
|
||||||
let Flags::Args(args) = flags;
|
let Flags::Args(args) = flags;
|
||||||
|
|
@ -72,8 +84,9 @@ impl cosmic::Application for Noctua {
|
||||||
// Initialize empty nav bar (for folder/thumbnail navigation later).
|
// Initialize empty nav bar (for folder/thumbnail navigation later).
|
||||||
let nav = nav_bar::Model::default();
|
let nav = nav_bar::Model::default();
|
||||||
|
|
||||||
// Context drawer hidden by default.
|
// Apply persisted panel states.
|
||||||
core.window.show_context = false;
|
core.window.show_context = config.context_drawer_visible;
|
||||||
|
core.nav_bar_set_toggled(config.nav_bar_visible);
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -81,6 +94,8 @@ impl cosmic::Application for Noctua {
|
||||||
model,
|
model,
|
||||||
nav,
|
nav,
|
||||||
context_page: ContextPage::default(),
|
context_page: ContextPage::default(),
|
||||||
|
config,
|
||||||
|
config_handler,
|
||||||
},
|
},
|
||||||
Task::none(),
|
Task::none(),
|
||||||
)
|
)
|
||||||
|
|
@ -91,57 +106,61 @@ impl cosmic::Application for Noctua {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) -> Task<Action<Self::Message>> {
|
fn update(&mut self, message: Self::Message) -> Task<Action<Self::Message>> {
|
||||||
// Handle panel toggle messages.
|
match &message {
|
||||||
if let AppMessage::ToggleContextPage(page) = &message {
|
// Handle nav bar toggle.
|
||||||
if self.context_page == *page {
|
AppMessage::ToggleNavBar => {
|
||||||
self.core.window.show_context = !self.core.window.show_context;
|
self.config.nav_bar_visible = !self.config.nav_bar_visible;
|
||||||
} else {
|
self.core.nav_bar_set_toggled(self.config.nav_bar_visible);
|
||||||
self.context_page = *page;
|
self.save_config();
|
||||||
self.core.window.show_context = true;
|
return Task::none();
|
||||||
}
|
}
|
||||||
return Task::none();
|
|
||||||
|
// Handle context panel toggle.
|
||||||
|
AppMessage::ToggleContextPage(page) => {
|
||||||
|
if self.context_page == *page {
|
||||||
|
self.core.window.show_context = !self.core.window.show_context;
|
||||||
|
} else {
|
||||||
|
self.context_page = *page;
|
||||||
|
self.core.window.show_context = true;
|
||||||
|
}
|
||||||
|
self.config.context_drawer_visible = self.core.window.show_context;
|
||||||
|
self.save_config();
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
update::update(&mut self.model, message);
|
update::update(&mut self.model, message);
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn header_start(&self) -> Vec<Element<Self::Message>> {
|
||||||
|
view::header::header_start(&self.model)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
||||||
|
view::header::header_end(&self.model)
|
||||||
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Self::Message> {
|
fn view(&self) -> Element<Self::Message> {
|
||||||
view::view(&self.model)
|
view::view(&self.model)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view_window(&self, _id: window::Id) -> Element<Self::Message> {
|
|
||||||
self.view()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Header end items (right side of header bar).
|
|
||||||
fn header_end(&self) -> Vec<Element<Self::Message>> {
|
|
||||||
vec![
|
|
||||||
// Properties panel toggle button.
|
|
||||||
button::icon(icon::from_name("document-properties-symbolic"))
|
|
||||||
.on_press(AppMessage::ToggleContextPage(ContextPage::Properties))
|
|
||||||
.into(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Right-side context drawer (properties panel).
|
|
||||||
fn context_drawer(&self) -> Option<context_drawer::ContextDrawer<Self::Message>> {
|
fn context_drawer(&self) -> Option<context_drawer::ContextDrawer<Self::Message>> {
|
||||||
if !self.core.window.show_context {
|
if !self.core.window.show_context {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(context_drawer::context_drawer(
|
Some(context_drawer::context_drawer(
|
||||||
view::panels::properties_panel(&self.model),
|
view::panels::properties_panel(&self.model),
|
||||||
AppMessage::ToggleContextPage(ContextPage::Properties),
|
AppMessage::ToggleContextPage(ContextPage::Properties),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Nav bar model for left panel.
|
|
||||||
fn nav_model(&self) -> Option<&nav_bar::Model> {
|
fn nav_model(&self) -> Option<&nav_bar::Model> {
|
||||||
Some(&self.nav)
|
Some(&self.nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Footer with zoom controls and document info.
|
|
||||||
fn footer(&self) -> Option<Element<Self::Message>> {
|
fn footer(&self) -> Option<Element<Self::Message>> {
|
||||||
Some(view::footer::view(&self.model))
|
Some(view::footer::view(&self.model))
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +170,15 @@ impl cosmic::Application for Noctua {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Noctua {
|
||||||
|
/// Save current config to disk.
|
||||||
|
fn save_config(&self) {
|
||||||
|
if let Some(ref handler) = self.config_handler {
|
||||||
|
let _ = self.config.write_entry(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Map raw key presses + modifiers into high-level application messages.
|
/// Map raw key presses + modifiers into high-level application messages.
|
||||||
fn handle_key_press(key: Key, modifiers: Modifiers) -> Option<AppMessage> {
|
fn handle_key_press(key: Key, modifiers: Modifiers) -> Option<AppMessage> {
|
||||||
use AppMessage::*;
|
use AppMessage::*;
|
||||||
|
|
@ -200,10 +228,11 @@ fn handle_key_press(key: Key, modifiers: Modifiers) -> Option<AppMessage> {
|
||||||
// Reset pan.
|
// Reset pan.
|
||||||
Key::Character("0") => Some(PanReset),
|
Key::Character("0") => Some(PanReset),
|
||||||
|
|
||||||
// Toggle properties panel with 'i' for info.
|
// Toggle panels.
|
||||||
Key::Character(ch) if ch.eq_ignore_ascii_case("i") => {
|
Key::Character(ch) if ch.eq_ignore_ascii_case("i") => {
|
||||||
Some(ToggleContextPage(ContextPage::Properties))
|
Some(ToggleContextPage(ContextPage::Properties))
|
||||||
}
|
}
|
||||||
|
Key::Character(ch) if ch.eq_ignore_ascii_case("n") => Some(ToggleNavBar),
|
||||||
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,10 @@ pub fn update(model: &mut AppModel, msg: AppMessage) {
|
||||||
// Handled in Noctua::update() directly.
|
// Handled in Noctua::update() directly.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppMessage::ToggleNavBar => {
|
||||||
|
// Handled in Noctua::update() directly.
|
||||||
|
}
|
||||||
|
|
||||||
AppMessage::NoOp => {
|
AppMessage::NoOp => {
|
||||||
// Intentionally do nothing.
|
// Intentionally do nothing.
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
src/app/view/header.rs
Normal file
58
src/app/view/header.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
// src/app/view/header.rs
|
||||||
|
//
|
||||||
|
// Header bar buttons (navigation, rotation, flip).
|
||||||
|
|
||||||
|
use cosmic::iced::Length;
|
||||||
|
use cosmic::widget::{button, horizontal_space, icon};
|
||||||
|
use cosmic::Element;
|
||||||
|
|
||||||
|
use crate::app::message::AppMessage;
|
||||||
|
use crate::app::model::AppModel;
|
||||||
|
use crate::app::ContextPage;
|
||||||
|
|
||||||
|
/// Build the left side of the header bar.
|
||||||
|
pub fn header_start(model: &AppModel) -> Vec<Element<AppMessage>> {
|
||||||
|
let has_doc = model.document.is_some();
|
||||||
|
|
||||||
|
vec![
|
||||||
|
// Nav bar toggle
|
||||||
|
button::icon(icon::from_name("view-sidebar-start-symbolic"))
|
||||||
|
.on_press(AppMessage::ToggleNavBar)
|
||||||
|
.into(),
|
||||||
|
// Spacer
|
||||||
|
horizontal_space().width(Length::Fixed(12.0)).into(),
|
||||||
|
// Navigation: previous / next
|
||||||
|
button::icon(icon::from_name("go-previous-symbolic"))
|
||||||
|
.on_press_maybe(has_doc.then_some(AppMessage::PrevDocument))
|
||||||
|
.into(),
|
||||||
|
button::icon(icon::from_name("go-next-symbolic"))
|
||||||
|
.on_press_maybe(has_doc.then_some(AppMessage::NextDocument))
|
||||||
|
.into(),
|
||||||
|
// Spacer
|
||||||
|
horizontal_space().width(Length::Fixed(12.0)).into(),
|
||||||
|
// Rotation: counter-clockwise / clockwise
|
||||||
|
button::icon(icon::from_name("object-rotate-left-symbolic"))
|
||||||
|
.on_press_maybe(has_doc.then_some(AppMessage::RotateCCW))
|
||||||
|
.into(),
|
||||||
|
button::icon(icon::from_name("object-rotate-right-symbolic"))
|
||||||
|
.on_press_maybe(has_doc.then_some(AppMessage::RotateCW))
|
||||||
|
.into(),
|
||||||
|
// Spacer
|
||||||
|
horizontal_space().width(Length::Fixed(12.0)).into(),
|
||||||
|
// Flip: horizontal / vertical
|
||||||
|
button::icon(icon::from_name("object-flip-horizontal-symbolic"))
|
||||||
|
.on_press_maybe(has_doc.then_some(AppMessage::FlipHorizontal))
|
||||||
|
.into(),
|
||||||
|
button::icon(icon::from_name("object-flip-vertical-symbolic"))
|
||||||
|
.on_press_maybe(has_doc.then_some(AppMessage::FlipVertical))
|
||||||
|
.into(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the right side of the header bar.
|
||||||
|
pub fn header_end(model: &AppModel) -> Vec<Element<AppMessage>> {
|
||||||
|
vec![button::icon(icon::from_name("dialog-information-symbolic"))
|
||||||
|
.on_press(AppMessage::ToggleContextPage(ContextPage::Properties))
|
||||||
|
.into()]
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
mod canvas;
|
mod canvas;
|
||||||
pub mod footer;
|
pub mod footer;
|
||||||
|
pub mod header;
|
||||||
pub mod panels;
|
pub mod panels;
|
||||||
|
|
||||||
use cosmic::Element;
|
use cosmic::Element;
|
||||||
|
|
|
||||||
|
|
@ -3,71 +3,103 @@
|
||||||
//
|
//
|
||||||
// Panel content for COSMIC context drawer.
|
// Panel content for COSMIC context drawer.
|
||||||
|
|
||||||
use cosmic::widget::{column, row, text};
|
use cosmic::widget::{column, divider, row, text};
|
||||||
use cosmic::Element;
|
use cosmic::Element;
|
||||||
|
|
||||||
use crate::app::document::DocumentContent;
|
|
||||||
use crate::app::{AppMessage, AppModel};
|
use crate::app::{AppMessage, AppModel};
|
||||||
use crate::fl;
|
use crate::fl;
|
||||||
|
|
||||||
/// Content for the right-side properties panel (context drawer).
|
/// Content for the right-side properties panel (context drawer).
|
||||||
pub fn properties_panel(model: &AppModel) -> Element<'static, AppMessage> {
|
pub fn properties_panel(model: &AppModel) -> Element<'static, AppMessage> {
|
||||||
let mut content = column::with_capacity(6).spacing(12);
|
let mut content = column::with_capacity(16).spacing(8);
|
||||||
|
|
||||||
// Header.
|
// Header.
|
||||||
let header = fl!("panel-properties");
|
content = content.push(text::title4(fl!("panel-properties")));
|
||||||
content = content.push(text::title4(header));
|
|
||||||
|
|
||||||
// Display document metadata if available.
|
// Display document metadata if available.
|
||||||
if let Some(ref doc) = model.document {
|
if let Some(ref doc) = model.document {
|
||||||
match doc {
|
// Use the unified interface to extract metadata.
|
||||||
DocumentContent::Raster(raster) => {
|
let meta = doc.extract_meta();
|
||||||
let (w, h) = raster.dimensions();
|
|
||||||
let format_str = raster
|
|
||||||
.path
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|p| p.extension())
|
|
||||||
.and_then(|e| e.to_str())
|
|
||||||
.unwrap_or("unknown")
|
|
||||||
.to_uppercase();
|
|
||||||
|
|
||||||
let lbl_dim = fl!("meta-dimensions");
|
// --- Basic Information Section ---
|
||||||
let lbl_fmt = fl!("meta-format");
|
content = content
|
||||||
|
.push(section_header(fl!("meta-section-file")))
|
||||||
|
.push(meta_row(fl!("meta-filename"), meta.basic.file_name.clone()))
|
||||||
|
.push(meta_row(fl!("meta-format"), meta.basic.format.clone()))
|
||||||
|
.push(meta_row(
|
||||||
|
fl!("meta-dimensions"),
|
||||||
|
meta.basic.resolution_display(),
|
||||||
|
))
|
||||||
|
.push(meta_row(
|
||||||
|
fl!("meta-filesize"),
|
||||||
|
meta.basic.file_size_display(),
|
||||||
|
))
|
||||||
|
.push(meta_row(
|
||||||
|
fl!("meta-colortype"),
|
||||||
|
meta.basic.color_type.clone(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// --- EXIF Section (if available) ---
|
||||||
|
if let Some(ref exif) = meta.exif {
|
||||||
|
let has_exif_data = exif.camera_display().is_some()
|
||||||
|
|| exif.date_time.is_some()
|
||||||
|
|| exif.exposure_time.is_some()
|
||||||
|
|| exif.f_number.is_some()
|
||||||
|
|| exif.iso.is_some()
|
||||||
|
|| exif.focal_length.is_some()
|
||||||
|
|| exif.gps_display().is_some();
|
||||||
|
|
||||||
|
if has_exif_data {
|
||||||
content = content
|
content = content
|
||||||
.push(meta_row(lbl_dim, format!("{}×{}", w, h)))
|
.push(divider::horizontal::light())
|
||||||
.push(meta_row(lbl_fmt, format_str));
|
.push(section_header(fl!("meta-section-exif")));
|
||||||
}
|
|
||||||
DocumentContent::Vector(vector) => {
|
|
||||||
let (w, h) = vector.dimensions();
|
|
||||||
|
|
||||||
let lbl_dim = fl!("meta-dimensions");
|
if let Some(camera) = exif.camera_display() {
|
||||||
let lbl_fmt = fl!("meta-format");
|
content = content.push(meta_row(fl!("meta-camera"), camera));
|
||||||
|
}
|
||||||
|
|
||||||
content = content
|
if let Some(ref date) = exif.date_time {
|
||||||
.push(meta_row(lbl_dim, format!("{}×{}", w, h)))
|
content = content.push(meta_row(fl!("meta-datetime"), date.clone()));
|
||||||
.push(meta_row(lbl_fmt, "SVG".to_string()));
|
}
|
||||||
}
|
|
||||||
DocumentContent::Portable(portable) => {
|
|
||||||
let lbl_pages = fl!("meta-pages");
|
|
||||||
let lbl_current = fl!("meta-current-page");
|
|
||||||
|
|
||||||
content = content
|
if let Some(ref exposure) = exif.exposure_time {
|
||||||
.push(meta_row(lbl_pages, portable.page_count.to_string()))
|
content = content.push(meta_row(fl!("meta-exposure"), exposure.clone()));
|
||||||
.push(meta_row(
|
}
|
||||||
lbl_current,
|
|
||||||
(portable.current_page + 1).to_string(),
|
if let Some(ref fnumber) = exif.f_number {
|
||||||
));
|
content = content.push(meta_row(fl!("meta-aperture"), fnumber.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(iso) = exif.iso {
|
||||||
|
content = content.push(meta_row(fl!("meta-iso"), format!("ISO {}", iso)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref focal) = exif.focal_length {
|
||||||
|
content = content.push(meta_row(fl!("meta-focal"), focal.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(gps) = exif.gps_display() {
|
||||||
|
content = content.push(meta_row(fl!("meta-gps"), gps));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- File Path (at the bottom, less prominent) ---
|
||||||
|
content = content
|
||||||
|
.push(divider::horizontal::light())
|
||||||
|
.push(meta_row_small(fl!("meta-path"), meta.basic.file_path.clone()));
|
||||||
} else {
|
} else {
|
||||||
let no_doc = fl!("no-document");
|
content = content.push(text::body(fl!("no-document")));
|
||||||
content = content.push(text::body(no_doc));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
content.into()
|
content.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Section header for grouping metadata.
|
||||||
|
fn section_header(label: String) -> Element<'static, AppMessage> {
|
||||||
|
text::body(label).into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper to create a key-value metadata row.
|
/// Helper to create a key-value metadata row.
|
||||||
fn meta_row(label: String, value: String) -> Element<'static, AppMessage> {
|
fn meta_row(label: String, value: String) -> Element<'static, AppMessage> {
|
||||||
row::with_capacity(2)
|
row::with_capacity(2)
|
||||||
|
|
@ -76,3 +108,12 @@ fn meta_row(label: String, value: String) -> Element<'static, AppMessage> {
|
||||||
.push(text::body(value))
|
.push(text::body(value))
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for less prominent metadata (smaller text, e.g., file path).
|
||||||
|
fn meta_row_small(label: String, value: String) -> Element<'static, AppMessage> {
|
||||||
|
column::with_capacity(2)
|
||||||
|
.spacing(2)
|
||||||
|
.push(text::caption(format!("{}:", label)))
|
||||||
|
.push(text::caption(value))
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
// src/config.rs
|
// src/config.rs
|
||||||
|
//
|
||||||
|
// Global configuration for the application with cosmic-config support.
|
||||||
|
|
||||||
use cosmic::cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
|
use cosmic::cosmic_config::{self, CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -10,6 +12,10 @@ use std::path::PathBuf;
|
||||||
pub struct AppConfig {
|
pub struct AppConfig {
|
||||||
/// Optional default directory to open images from.
|
/// Optional default directory to open images from.
|
||||||
pub default_image_dir: Option<PathBuf>,
|
pub default_image_dir: Option<PathBuf>,
|
||||||
|
/// Whether the nav bar (left panel) is visible.
|
||||||
|
pub nav_bar_visible: bool,
|
||||||
|
/// Whether the context drawer (right panel) is visible.
|
||||||
|
pub context_drawer_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfig {
|
impl Default for AppConfig {
|
||||||
|
|
@ -17,6 +23,8 @@ impl Default for AppConfig {
|
||||||
Self {
|
Self {
|
||||||
// TODO: Use xdg dir for picture
|
// TODO: Use xdg dir for picture
|
||||||
default_image_dir: Some(PathBuf::from("~/Pictures")),
|
default_image_dir: Some(PathBuf::from("~/Pictures")),
|
||||||
|
nav_bar_visible: false,
|
||||||
|
context_drawer_visible: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue