chore: refactoring
This commit is contained in:
parent
aa83e9ab1d
commit
b1b0999ebe
11 changed files with 287 additions and 285 deletions
|
|
@ -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)
|
||||
|
|
|
|||
76
src/app/view/footer.rs
Normal file
76
src/app/view/footer.rs
Normal file
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Element<'_, AppMessage>> {
|
||||
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<Element<'_, AppMessage>> {
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue