diff --git a/i18n/en/noctua.ftl b/i18n/en/noctua.ftl
index f4aaa52..a7796dc 100644
--- a/i18n/en/noctua.ftl
+++ b/i18n/en/noctua.ftl
@@ -25,8 +25,8 @@ menu-view-flip-vertical = Flip Vertically
menu-view-rotate-cw = Rotate Clockwise
menu-view-rotate-ccw = Rotate Counter-Clockwise
-## Note messages
-no_document_loaded = No document loaded.
+## Placeholders / empty states
+no-document = No document loaded
## Labels
zoom = Zoom
@@ -35,10 +35,17 @@ crop = Crop
scale = Scale
## Error messages
-error-failed-to-open = Failed to open “{ $path }”.
+error-failed-to-open = Failed to open "{ $path }".
error-unsupported-format = Unsupported file format.
-# Metadata panel
+## Properties panel
+panel-properties = Properties
+meta-dimensions = Dimensions
+meta-format = Format
+meta-pages = Pages
+meta-current-page = Current Page
+
+## Metadata panel (extended)
metadata = Metadata
file-name = File
format = Format
@@ -46,7 +53,7 @@ resolution = Resolution
file-size = Size
color-type = Color
-# EXIF data
+## EXIF data
exif-data = EXIF Data
camera = Camera
date-taken = Date
@@ -56,6 +63,5 @@ iso = ISO
focal-length = Focal
gps = GPS
-# States
+## States
loading-metadata = Loading...
-no-document = No document
diff --git a/resources/icons/hicolor/scalable/actions/panel-left-symbolic.svg b/resources/icons/hicolor/scalable/actions/panel-left-symbolic.svg
new file mode 100644
index 0000000..4932d7a
--- /dev/null
+++ b/resources/icons/hicolor/scalable/actions/panel-left-symbolic.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/resources/icons/hicolor/scalable/actions/panel-right-symbolic.svg b/resources/icons/hicolor/scalable/actions/panel-right-symbolic.svg
new file mode 100644
index 0000000..6f86e6d
--- /dev/null
+++ b/resources/icons/hicolor/scalable/actions/panel-right-symbolic.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/src/app/message.rs b/src/app/message.rs
index 5a80983..9b1cb95 100644
--- a/src/app/message.rs
+++ b/src/app/message.rs
@@ -1,57 +1,73 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// src/app/message.rs
//
-// Top-level application messages (events, IO, and UI signals).
+// All application messages (events, user actions, signals).
use std::path::PathBuf;
-/// Top-level application messages.
-///
-/// These are produced by:
-/// - UI widgets (buttons, menus, etc.)
-/// - keyboard shortcuts
-/// - async tasks (file loading, etc.)
+use crate::app::ContextPage;
+
+/// Messages emitted by user actions, async I/O, or internal signals.
#[derive(Debug, Clone)]
pub enum AppMessage {
- /// User requested to open a single file.
+ // === File / Navigation ===
+ /// Open a file at the given path.
OpenPath(PathBuf),
-
- /// Navigate to next/previous document in the current folder.
+ /// Navigate to the next document in folder.
NextDocument,
+ /// Navigate to the previous document in folder.
PrevDocument,
- /// Refresh metadata (e.g., when panel becomes visible or document changes).
- RefreshMetadata,
+ // === Transformations ===
+ /// Rotate 90° clockwise.
+ RotateCW,
+ /// Rotate 90° counter-clockwise.
+ RotateCCW,
+ /// Flip horizontally (mirror).
+ FlipHorizontal,
+ /// Flip vertically.
+ FlipVertical,
- /// Basic view / panel toggles.
- ToggleLeftPanel,
- ToggleRightPanel,
-
- /// View / zoom control.
+ // === Zoom ===
+ /// Zoom in by a fixed step.
ZoomIn,
+ /// Zoom out by a fixed step.
ZoomOut,
+ /// Reset zoom to 100%.
ZoomReset,
+ /// Fit document to window.
ZoomFit,
- /// Pan control (Ctrl + arrow keys).
+ // === Pan ===
+ /// Pan image left.
PanLeft,
+ /// Pan image right.
PanRight,
+ /// Pan image up.
PanUp,
+ /// Pan image down.
PanDown,
+ /// Reset pan to center.
PanReset,
- /// Editing / tool modes.
+ // === Tool Modes ===
+ /// Toggle crop mode.
ToggleCropMode,
+ /// Toggle scale mode.
ToggleScaleMode,
- /// Document transformations.
- FlipHorizontal,
- FlipVertical,
- RotateCW,
- RotateCCW,
+ // === Panels (COSMIC-managed) ===
+ /// Toggle a context drawer page.
+ ToggleContextPage(ContextPage),
- /// Generic error reporting from lower layers.
+ // === Metadata ===
+ /// Refresh metadata from the current document.
+ RefreshMetadata,
+
+ // === Errors ===
+ /// Display an error message.
ShowError(String),
+ /// Clear the current error.
ClearError,
/// Fallback for unhandled or no-op cases.
diff --git a/src/app/mod.rs b/src/app/mod.rs
index a2b09ab..b403c76 100644
--- a/src/app/mod.rs
+++ b/src/app/mod.rs
@@ -10,10 +10,11 @@ pub mod update;
mod view;
-use cosmic::app::Core;
+use cosmic::app::{context_drawer, Core};
use cosmic::iced::keyboard::{self, key::Named, Key, Modifiers};
use cosmic::iced::window;
use cosmic::iced::Subscription;
+use cosmic::widget::{button, icon, nav_bar};
use cosmic::{Action, Element, Task};
pub use message::AppMessage;
@@ -28,10 +29,19 @@ pub enum Flags {
Args(Args),
}
+/// Context page displayed in right drawer.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
+pub enum ContextPage {
+ #[default]
+ Properties,
+}
+
/// Main application type.
pub struct Noctua {
core: Core,
pub model: AppModel,
+ nav: nav_bar::Model,
+ context_page: ContextPage,
}
impl cosmic::Application for Noctua {
@@ -49,7 +59,7 @@ impl cosmic::Application for Noctua {
&mut self.core
}
- fn init(core: Core, flags: Self::Flags) -> (Self, Task>) {
+ fn init(mut core: Core, flags: Self::Flags) -> (Self, Task>) {
let config = AppConfig::default();
let mut model = AppModel::new(config);
@@ -59,7 +69,21 @@ impl cosmic::Application for Noctua {
document::file::open_initial_path(&mut model, path);
}
- (Self { core, model }, Task::none())
+ // Initialize empty nav bar (for folder/thumbnail navigation later).
+ let nav = nav_bar::Model::default();
+
+ // Context drawer hidden by default.
+ core.window.show_context = false;
+
+ (
+ Self {
+ core,
+ model,
+ nav,
+ context_page: ContextPage::default(),
+ },
+ Task::none(),
+ )
}
fn on_close_requested(&self, _id: window::Id) -> Option {
@@ -67,6 +91,17 @@ impl cosmic::Application for Noctua {
}
fn update(&mut self, message: Self::Message) -> Task> {
+ // Handle panel toggle messages.
+ if let AppMessage::ToggleContextPage(page) = &message {
+ 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;
+ }
+ return Task::none();
+ }
+
update::update(&mut self.model, message);
Task::none()
}
@@ -79,6 +114,38 @@ impl cosmic::Application for Noctua {
self.view()
}
+ /// Header end items (right side of header bar).
+ fn header_end(&self) -> Vec> {
+ 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> {
+ if !self.core.window.show_context {
+ return None;
+ }
+
+ Some(context_drawer::context_drawer(
+ view::panels::properties_panel(&self.model),
+ AppMessage::ToggleContextPage(ContextPage::Properties),
+ ))
+ }
+
+ /// Nav bar model for left panel.
+ fn nav_model(&self) -> Option<&nav_bar::Model> {
+ Some(&self.nav)
+ }
+
+ /// Footer with zoom controls and document info.
+ fn footer(&self) -> Option> {
+ Some(view::footer::view(&self.model))
+ }
+
fn subscription(&self) -> Subscription {
keyboard::on_key_press(handle_key_press)
}
@@ -133,6 +200,11 @@ fn handle_key_press(key: Key, modifiers: Modifiers) -> Option {
// Reset pan.
Key::Character("0") => Some(PanReset),
+ // Toggle properties panel with 'i' for info.
+ Key::Character(ch) if ch.eq_ignore_ascii_case("i") => {
+ Some(ToggleContextPage(ContextPage::Properties))
+ }
+
_ => None,
}
}
diff --git a/src/app/model.rs b/src/app/model.rs
index a09c2d6..f475d94 100644
--- a/src/app/model.rs
+++ b/src/app/model.rs
@@ -5,9 +5,8 @@
use std::path::PathBuf;
-use crate::app::document::DocumentContent;
use crate::app::document::meta::DocumentMeta;
-
+use crate::app::document::DocumentContent;
use crate::config::AppConfig;
/// How the document is currently fitted into the window.
@@ -54,7 +53,7 @@ pub struct AppModel {
pub document: Option,
/// Cached metadata for the current document.
- /// Loaded lazily when the right panel is opened.
+ /// Loaded lazily when the metadata panel is opened.
pub metadata: Option,
/// Path of the currently opened document, if any.
@@ -73,10 +72,6 @@ pub struct AppModel {
pub pan_x: f32,
pub pan_y: f32,
- /// Panel visibility.
- pub show_left_panel: bool,
- pub show_right_panel: bool,
-
/// Current tool mode.
pub tool_mode: ToolMode,
@@ -97,8 +92,6 @@ impl AppModel {
view_mode: ViewMode::Fit,
pan_x: 0.0,
pan_y: 0.0,
- show_left_panel: false,
- show_right_panel: false,
tool_mode: ToolMode::None,
error: None,
}
diff --git a/src/app/update.rs b/src/app/update.rs
index af98bd2..9e52160 100644
--- a/src/app/update.rs
+++ b/src/app/update.rs
@@ -9,46 +9,21 @@ use super::model::{AppModel, ToolMode, ViewMode, PAN_STEP};
/// Central update function applying messages to the model.
///
-/// This is the single place where application state is mutated.
+/// Panel toggle messages (ToggleContextPage) are handled directly in
+/// `Noctua::update()` since they affect COSMIC's Core state.
pub fn update(model: &mut AppModel, msg: AppMessage) {
- println!("update(): received message: {:?}", msg);
-
match msg {
// ===== File / navigation ==========================================================
AppMessage::OpenPath(path) => {
document::file::open_single_file(model, &path);
- // Refresh metadata if panel is visible.
- if model.show_right_panel {
- refresh_metadata(model);
- }
}
AppMessage::NextDocument => {
document::file::navigate_next(model);
- // Refresh metadata if panel is visible.
- if model.show_right_panel {
- refresh_metadata(model);
- }
}
AppMessage::PrevDocument => {
document::file::navigate_prev(model);
- // Refresh metadata if panel is visible.
- if model.show_right_panel {
- refresh_metadata(model);
- }
- }
-
- // ===== Panels =====================================================================
- AppMessage::ToggleLeftPanel => {
- model.show_left_panel = !model.show_left_panel;
- }
- AppMessage::ToggleRightPanel => {
- model.show_right_panel = !model.show_right_panel;
- // Load metadata lazily when panel becomes visible.
- if model.show_right_panel && model.metadata.is_none() {
- refresh_metadata(model);
- }
}
// ===== View / zoom ===============================================================
@@ -80,7 +55,7 @@ pub fn update(model: &mut AppModel, msg: AppMessage) {
model.reset_pan();
}
- // ===== Tools =====================================================================
+ // ===== Tool modes ================================================================
AppMessage::ToggleCropMode => {
model.tool_mode = if model.tool_mode == ToolMode::Crop {
ToolMode::None
@@ -131,6 +106,11 @@ pub fn update(model: &mut AppModel, msg: AppMessage) {
model.clear_error();
}
+ // ===== Handled elsewhere =========================================================
+ AppMessage::ToggleContextPage(_) => {
+ // Handled in Noctua::update() directly.
+ }
+
AppMessage::NoOp => {
// Intentionally do nothing.
}
@@ -152,7 +132,6 @@ fn zoom_out(model: &mut AppModel) {
}
/// Extract the current effective zoom factor from the view mode.
-/// For `Fit` mode, we assume 1.0 as starting point when switching to custom zoom.
fn current_zoom(model: &AppModel) -> f32 {
match model.view_mode {
ViewMode::Fit => 1.0,
diff --git a/src/app/view/canvas.rs b/src/app/view/canvas.rs
index 702f8c9..e49107c 100644
--- a/src/app/view/canvas.rs
+++ b/src/app/view/canvas.rs
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// src/app/view/canvas.rs
//
-/// Renders the center canvas area with the current document.
-//
+// Renders the center canvas area with the current document.
+
use cosmic::iced::{Alignment, Length};
use cosmic::widget::{container, image, text, Column, Row};
use cosmic::Element;
@@ -55,7 +55,8 @@ pub fn view(model: &AppModel) -> Element<'_, AppMessage> {
)
.into()
} else {
- container(text(fl!("no_document_loaded")))
+ // No document loaded placeholder.
+ container(text(fl!("no-document")))
.center_x(Length::Fill)
.center_y(Length::Fill)
.width(Length::Fill)
diff --git a/src/app/view/footer.rs b/src/app/view/footer.rs
new file mode 100644
index 0000000..8dec49b
--- /dev/null
+++ b/src/app/view/footer.rs
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// src/app/view/footer.rs
+//
+// Footer bar with zoom controls and document info.
+
+use cosmic::iced::Alignment;
+use cosmic::widget::{button, icon, row, text};
+use cosmic::Element;
+
+use crate::app::model::{AppModel, ViewMode};
+use crate::app::AppMessage;
+
+/// Build the footer element with zoom controls and document info.
+pub fn view(model: &AppModel) -> Element<'_, AppMessage> {
+ // Zoom level display.
+ let zoom_text = match model.view_mode {
+ ViewMode::Fit => "Fit".to_string(),
+ ViewMode::ActualSize => "100%".to_string(),
+ ViewMode::Custom(z) => format!("{}%", (z * 100.0).round() as i32),
+ };
+
+ // Document dimensions (if available).
+ let doc_info = if let Some(ref doc) = model.document {
+ let (w, h) = doc.dimensions();
+ format!("{}×{}", w, h)
+ } else {
+ String::new()
+ };
+
+ // Navigation position (e.g., "3 / 42").
+ let nav_info = if !model.folder_entries.is_empty() {
+ let current = model.current_index.map(|i| i + 1).unwrap_or(0);
+ let total = model.folder_entries.len();
+ format!("{} / {}", current, total)
+ } else {
+ String::new()
+ };
+
+ row()
+ .spacing(8)
+ .align_y(Alignment::Center)
+ .padding([4, 12])
+ // Zoom out button.
+ .push(
+ button::icon(icon::from_name("zoom-out-symbolic"))
+ .on_press(AppMessage::ZoomOut)
+ .padding(4),
+ )
+ // Zoom level display.
+ .push(text::body(zoom_text))
+ // Zoom in button.
+ .push(
+ button::icon(icon::from_name("zoom-in-symbolic"))
+ .on_press(AppMessage::ZoomIn)
+ .padding(4),
+ )
+ // Fit button.
+ .push(
+ button::icon(icon::from_name("zoom-fit-best-symbolic"))
+ .on_press(AppMessage::ZoomFit)
+ .padding(4),
+ )
+ // Spacer.
+ .push(cosmic::widget::horizontal_space())
+ // Document dimensions.
+ .push(text::body(doc_info))
+ // Separator.
+ .push_maybe(if !model.folder_entries.is_empty() {
+ Some(text::body(" | "))
+ } else {
+ None
+ })
+ // Navigation position.
+ .push(text::body(nav_info))
+ .into()
+}
diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs
index 59997cd..b2b0e59 100644
--- a/src/app/view/mod.rs
+++ b/src/app/view/mod.rs
@@ -1,49 +1,17 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// src/app/view/mod.rs
//
-// Root layout for the main application window.
+// View module root, combining all view components.
-pub mod canvas;
+mod canvas;
+pub mod footer;
pub mod panels;
use cosmic::Element;
-use cosmic::iced::Length;
-use cosmic::widget::{Column, Container, Row};
use crate::app::{AppMessage, AppModel};
-/// Main window layout (header, center row, footer).
+/// Main application view (canvas area).
pub fn view(model: &AppModel) -> Element<'_, AppMessage> {
- let header = panels::header(model);
- let footer = panels::footer(model);
- let left_panel = panels::left_panel(model);
- let right_panel = panels::right_panel(model);
- let canvas = canvas::view(model);
-
- // Build middle row step by step to handle optional panels.
- let mut middle_row = Row::new().spacing(8).height(Length::Fill);
-
- if let Some(left) = left_panel {
- middle_row = middle_row.push(left);
- }
-
- middle_row = middle_row.push(canvas);
-
- if let Some(right) = right_panel {
- middle_row = middle_row.push(right);
- }
-
- let content = Column::new()
- .spacing(8)
- .padding(8)
- .width(Length::Fill)
- .height(Length::Fill)
- .push(header)
- .push(middle_row)
- .push(footer);
-
- Container::new(content)
- .width(Length::Fill)
- .height(Length::Fill)
- .into()
+ canvas::view(model)
}
diff --git a/src/app/view/panels.rs b/src/app/view/panels.rs
index cbe0068..b2f533b 100644
--- a/src/app/view/panels.rs
+++ b/src/app/view/panels.rs
@@ -1,197 +1,78 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// src/app/view/panels.rs
//
-// Header, footer, and side panels composing the main layout.
+// Panel content for COSMIC context drawer.
+use cosmic::widget::{column, row, text};
use cosmic::Element;
-use cosmic::iced::{Alignment, Length};
-use cosmic::widget::{self, Column, Container, Row, Text};
-use crate::fl;
-use crate::app::model::ViewMode;
+use crate::app::document::DocumentContent;
use crate::app::{AppMessage, AppModel};
+use crate::fl;
-/// Top header bar (global actions, toggles).
-pub fn header(model: &AppModel) -> Element<'_, AppMessage> {
- // Left panel toggle button.
- let left_toggle = widget::button::icon(widget::icon::from_name(if model.show_left_panel {
- "sidebar-show-left-symbolic"
- } else {
- "sidebar-show-left-symbolic"
- }))
- .on_press(AppMessage::ToggleLeftPanel);
+/// Content for the right-side properties panel (context drawer).
+pub fn properties_panel(model: &AppModel) -> Element<'static, AppMessage> {
+ let mut content = column::with_capacity(6).spacing(12);
- // Right panel toggle button.
- let right_toggle = widget::button::icon(widget::icon::from_name(if model.show_right_panel {
- "sidebar-show-right-symbolic"
- } else {
- "sidebar-show-right-symbolic"
- }))
- .on_press(AppMessage::ToggleRightPanel);
+ // Header.
+ let header = fl!("panel-properties");
+ content = content.push(text::title4(header));
- // File name display (centered).
- let file_name = model
- .current_path
- .as_ref()
- .and_then(|p| p.file_name())
- .and_then(|n| n.to_str())
- .unwrap_or("");
+ // Display document metadata if available.
+ if let Some(ref doc) = model.document {
+ match doc {
+ DocumentContent::Raster(raster) => {
+ 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 title = Text::new(file_name);
+ let lbl_dim = fl!("meta-dimensions");
+ let lbl_fmt = fl!("meta-format");
- // Spacer to push title to center and right_toggle to the right.
- let left_section = Row::new()
- .spacing(8)
- .align_y(Alignment::Center)
- .push(left_toggle);
-
- let center_section = Container::new(title)
- .width(Length::Fill)
- .align_x(Alignment::Center);
-
- let right_section = Row::new()
- .spacing(8)
- .align_y(Alignment::Center)
- .push(right_toggle);
-
- let content = Row::new()
- .spacing(8)
- .align_y(Alignment::Center)
- .width(Length::Fill)
- .push(left_section)
- .push(center_section)
- .push(right_section);
-
- Container::new(content)
- .width(Length::Fill)
- .padding([4, 8])
- .into()
-}
-
-/// Bottom footer bar (navigation & zoom).
-pub fn footer(model: &AppModel) -> Element<'_, AppMessage> {
- let nav = Row::new()
- .spacing(4)
- .align_y(Alignment::Center)
- .push(widget::button::standard("<").on_press(AppMessage::PrevDocument))
- .push(widget::button::standard(">").on_press(AppMessage::NextDocument));
-
- let zoom_text = match model.view_mode {
- ViewMode::Fit => "Fit".to_string(),
- ViewMode::ActualSize => "100%".to_string(),
- ViewMode::Custom(zoom_factor) => format!("{:.0}%", zoom_factor * 100.0),
- };
-
- let zoom_info = Text::new(format!("Zoom: {}", zoom_text));
-
- let content = Row::new()
- .spacing(16)
- .align_y(Alignment::Center)
- .push(nav)
- .push(zoom_info);
-
- Container::new(content)
- .width(Length::Fill)
- .padding([4, 8])
- .into()
-}
-
-/// Optional left panel (tools).
-pub fn left_panel(model: &AppModel) -> Option> {
- if !model.show_left_panel {
- return None;
- }
-
- let tools = Column::new()
- .spacing(4)
- .push(Text::new(fl!("tools")))
- .push(widget::button::standard(fl!("crop")).on_press(AppMessage::ToggleCropMode))
- .push(widget::button::standard(fl!("scale")).on_press(AppMessage::ToggleScaleMode));
-
- let panel = Container::new(tools)
- .width(Length::Fixed(180.0))
- .height(Length::Fill)
- .padding(8);
-
- Some(panel.into())
-}
-
-/// Optional right panel (metadata, info).
-pub fn right_panel(model: &AppModel) -> Option> {
- if !model.show_right_panel {
- return None;
- }
-
- let mut content = Column::new().spacing(8).padding(4);
-
- // Section header.
- content = content.push(Text::new(fl!("metadata")).size(16).width(Length::Fill));
-
- content = content.push(widget::divider::horizontal::default());
-
- if let Some(meta) = &model.metadata {
- // Basic information section.
- content = content
- .push(meta_row(fl!("file-name"), meta.basic.file_name.clone()))
- .push(meta_row(fl!("format"), meta.basic.format.clone()))
- .push(meta_row(fl!("resolution"), meta.basic.resolution_display()))
- .push(meta_row(fl!("file-size"), meta.basic.file_size_display()))
- .push(meta_row(fl!("color-type"), meta.basic.color_type.clone()));
-
- // EXIF section (if available).
- if let Some(exif) = &meta.exif {
- content = content
- .push(widget::vertical_space().height(Length::Fixed(12.0)))
- .push(Text::new(fl!("exif-data")).size(14))
- .push(widget::divider::horizontal::default());
-
- if let Some(camera) = exif.camera_display() {
- content = content.push(meta_row(fl!("camera"), camera));
+ content = content
+ .push(meta_row(lbl_dim, format!("{}×{}", w, h)))
+ .push(meta_row(lbl_fmt, format_str));
}
- if let Some(date) = &exif.date_time {
- content = content.push(meta_row(fl!("date-taken"), date.clone()));
+ DocumentContent::Vector(vector) => {
+ let (w, h) = vector.dimensions();
+
+ let lbl_dim = fl!("meta-dimensions");
+ let lbl_fmt = fl!("meta-format");
+
+ content = content
+ .push(meta_row(lbl_dim, format!("{}×{}", w, h)))
+ .push(meta_row(lbl_fmt, "SVG".to_string()));
}
- if let Some(exp) = &exif.exposure_time {
- content = content.push(meta_row(fl!("exposure"), exp.clone()));
- }
- if let Some(aperture) = &exif.f_number {
- content = content.push(meta_row(fl!("aperture"), aperture.clone()));
- }
- if let Some(iso) = exif.iso {
- content = content.push(meta_row(fl!("iso"), iso.to_string()));
- }
- if let Some(focal) = &exif.focal_length {
- content = content.push(meta_row(fl!("focal-length"), focal.clone()));
- }
- if let Some(gps) = exif.gps_display() {
- content = content.push(meta_row(fl!("gps"), gps));
+ DocumentContent::Portable(portable) => {
+ let lbl_pages = fl!("meta-pages");
+ let lbl_current = fl!("meta-current-page");
+
+ content = content
+ .push(meta_row(lbl_pages, portable.page_count.to_string()))
+ .push(meta_row(
+ lbl_current,
+ (portable.current_page + 1).to_string(),
+ ));
}
}
- } else if model.document.is_some() {
- // Document exists but metadata not yet loaded.
- content = content.push(Text::new(fl!("loading-metadata")));
} else {
- // No document loaded.
- content = content.push(Text::new(fl!("no-document")));
+ let no_doc = fl!("no-document");
+ content = content.push(text::body(no_doc));
}
- let panel = Container::new(widget::scrollable(content).height(Length::Fill))
- .width(Length::Fixed(240.0))
- .height(Length::Fill)
- .padding(8);
-
- Some(panel.into())
+ content.into()
}
-/// Helper to create a label-value row for metadata display.
+/// Helper to create a key-value metadata row.
fn meta_row(label: String, value: String) -> Element<'static, AppMessage> {
- Row::new()
+ row::with_capacity(2)
.spacing(8)
- .push(
- Text::new(format!("{}:", label))
- .size(12)
- .width(Length::Fixed(80.0)),
- )
- .push(Text::new(value).size(12).width(Length::Fill))
+ .push(text::body(format!("{}:", label)))
+ .push(text::body(value))
.into()
}