feat: finalizations for M0-M2

This commit is contained in:
2026-04-05 07:41:33 +02:00
parent e46294a022
commit ee61ad56ea
48 changed files with 1296 additions and 498 deletions

View File

@@ -5,4 +5,5 @@ pub mod tab_bar;
pub mod status_bar;
pub mod panel;
pub mod project_selector;
pub mod toast;
pub mod welcome;

View File

@@ -0,0 +1,104 @@
use iced::widget::{button, container, row, text, Space};
use iced::widget::text::Shaping;
use iced::{Alignment, Background, Border, Color, Element, Length, Padding, Theme};
use crate::app::Message;
use crate::state::toast::{Toast, ToastLevel};
/// Background color per toast severity.
fn toast_bg(level: ToastLevel) -> Color {
match level {
ToastLevel::Info => Color::from_rgb(0.16, 0.22, 0.34),
ToastLevel::Success => Color::from_rgb(0.12, 0.30, 0.16),
ToastLevel::Warning => Color::from_rgb(0.38, 0.30, 0.10),
ToastLevel::Error => Color::from_rgb(0.38, 0.14, 0.14),
}
}
/// Border color per toast severity.
fn toast_border(level: ToastLevel) -> Color {
match level {
ToastLevel::Info => Color::from_rgb(0.25, 0.40, 0.65),
ToastLevel::Success => Color::from_rgb(0.20, 0.55, 0.25),
ToastLevel::Warning => Color::from_rgb(0.65, 0.50, 0.15),
ToastLevel::Error => Color::from_rgb(0.65, 0.20, 0.20),
}
}
fn toast_style(level: ToastLevel) -> impl Fn(&Theme) -> container::Style {
move |_theme: &Theme| container::Style {
background: Some(Background::Color(toast_bg(level))),
border: Border {
color: toast_border(level),
width: 1.0,
radius: 4.0.into(),
},
..container::Style::default()
}
}
fn dismiss_btn(_theme: &Theme, status: button::Status) -> button::Style {
let color = match status {
button::Status::Hovered => Color::WHITE,
_ => Color::from_rgb(0.65, 0.65, 0.70),
};
button::Style {
background: Some(Background::Color(Color::TRANSPARENT)),
text_color: color,
border: Border::default(),
..button::Style::default()
}
}
/// Render the toast stack as an overlay element.
///
/// Returns `None` when no toasts are visible.
pub fn view(toasts: &[Toast]) -> Option<Element<'static, Message>> {
if toasts.is_empty() {
return None;
}
let items: Vec<Element<'static, Message>> = toasts
.iter()
.map(|toast| {
let level = toast.level;
let dismiss = button(text("\u{2715}").size(11).shaping(Shaping::Advanced))
.on_press(Message::DismissToast(toast.id))
.padding([2, 4])
.style(dismiss_btn);
container(
row![
text(toast.message.clone())
.size(12)
.shaping(Shaping::Advanced)
.color(Color::WHITE),
Space::with_width(Length::Fill),
dismiss,
]
.spacing(8)
.align_y(Alignment::Center)
.padding([6, 12]),
)
.width(Length::Fixed(420.0))
.style(toast_style(level))
.into()
})
.collect();
Some(
container(
container(
iced::widget::Column::with_children(items)
.spacing(4)
.align_x(Alignment::Center),
)
.width(Length::Fill)
.align_x(Alignment::Center)
.padding(Padding { top: 8.0, right: 0.0, bottom: 0.0, left: 0.0 }),
)
.width(Length::Fill)
.height(Length::Shrink)
.into(),
)
}

View File

@@ -8,7 +8,8 @@ use bds_core::model::{Media, Post, Project};
use crate::app::Message;
use crate::state::navigation::{OutputEntry, PanelTab, SidebarView, TaskSnapshot};
use crate::state::tabs::Tab;
use crate::views::{activity_bar, panel, project_selector, sidebar, status_bar, tab_bar, welcome};
use crate::state::toast::Toast;
use crate::views::{activity_bar, panel, project_selector, sidebar, status_bar, tab_bar, toast, welcome};
/// Main content area background.
fn content_bg(_theme: &Theme) -> container::Style {
@@ -67,6 +68,8 @@ pub fn view(
project_dropdown_open: bool,
// i18n
locale: UiLocale,
// Toasts
toasts: &[Toast],
) -> Element<'static, Message> {
// Activity bar (leftmost column)
let activity = activity_bar::view(sidebar_view, locale);
@@ -181,12 +184,25 @@ pub fn view(
None
};
// Collect overlays: dropdowns and toasts
let mut overlays: Vec<Element<'static, Message>> = Vec::new();
if let Some(toast_overlay) = toast::view(toasts) {
overlays.push(toast_overlay);
}
if let Some(overlay) = overlay {
stack![base_layout, overlay]
overlays.push(overlay);
}
if overlays.is_empty() {
base_layout
} else {
let mut layers = vec![base_layout];
layers.extend(overlays);
stack(layers)
.width(Length::Fill)
.height(Length::Fill)
.into()
} else {
base_layout
}
}