feat: more work on UI
135
Cargo.lock
generated
@@ -950,7 +950,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"fontdb",
|
||||
"fontdb 0.16.2",
|
||||
"log",
|
||||
"rangemap",
|
||||
"rayon",
|
||||
@@ -1073,6 +1073,12 @@ dependencies = [
|
||||
"zbus 4.4.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||
|
||||
[[package]]
|
||||
name = "dconf_rs"
|
||||
version = "0.3.0"
|
||||
@@ -1456,6 +1462,12 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1500,6 +1512,20 @@ dependencies = [
|
||||
"ttf-parser 0.20.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontdb"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e32eac81c1135c1df01d4e6d4233c47ba11f6a6d07f33e0bba09d18797077770"
|
||||
dependencies = [
|
||||
"fontconfig-parser",
|
||||
"log",
|
||||
"memmap2",
|
||||
"slotmap",
|
||||
"tinyvec",
|
||||
"ttf-parser 0.21.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
@@ -2256,8 +2282,9 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text",
|
||||
"iced_graphics",
|
||||
"kurbo",
|
||||
"kurbo 0.10.4",
|
||||
"log",
|
||||
"resvg",
|
||||
"rustc-hash 2.1.2",
|
||||
"softbuffer",
|
||||
"tiny-skia",
|
||||
@@ -2278,6 +2305,7 @@ dependencies = [
|
||||
"iced_graphics",
|
||||
"log",
|
||||
"once_cell",
|
||||
"resvg",
|
||||
"rustc-hash 2.1.2",
|
||||
"thiserror 1.0.69",
|
||||
"wgpu",
|
||||
@@ -2479,6 +2507,12 @@ dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imagesize"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.12.0"
|
||||
@@ -2687,6 +2721,17 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"euclid",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
@@ -3751,6 +3796,12 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.11"
|
||||
@@ -4327,6 +4378,22 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "944d052815156ac8fa77eaac055220e95ba0b01fa8887108ca710c03805d9051"
|
||||
dependencies = [
|
||||
"gif 0.13.3",
|
||||
"jpeg-decoder",
|
||||
"log",
|
||||
"pico-args",
|
||||
"rgb",
|
||||
"svgtypes",
|
||||
"tiny-skia",
|
||||
"usvg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfd"
|
||||
version = "0.15.4"
|
||||
@@ -4356,6 +4423,9 @@ name = "rgb"
|
||||
version = "0.8.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ropey"
|
||||
@@ -4674,6 +4744,15 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "simplecss"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
@@ -4857,6 +4936,9 @@ name = "strict-num"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
|
||||
dependencies = [
|
||||
"float-cmp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svg_fmt"
|
||||
@@ -4864,6 +4946,16 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb"
|
||||
|
||||
[[package]]
|
||||
name = "svgtypes"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
|
||||
dependencies = [
|
||||
"kurbo 0.11.3",
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.1.19"
|
||||
@@ -5357,6 +5449,12 @@ version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-vo"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
@@ -5394,6 +5492,33 @@ version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"data-url",
|
||||
"flate2",
|
||||
"fontdb 0.18.0",
|
||||
"imagesize",
|
||||
"kurbo 0.11.3",
|
||||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher",
|
||||
"strict-num",
|
||||
"svgtypes",
|
||||
"tiny-skia-path",
|
||||
"unicode-bidi",
|
||||
"unicode-script",
|
||||
"unicode-vo",
|
||||
"xmlwriter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8_iter"
|
||||
version = "1.0.4"
|
||||
@@ -6398,6 +6523,12 @@ version = "0.8.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
||||
|
||||
[[package]]
|
||||
name = "xmlwriter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
|
||||
|
||||
[[package]]
|
||||
name = "y4m"
|
||||
version = "0.8.0"
|
||||
|
||||
@@ -34,7 +34,7 @@ dirs = "5"
|
||||
open = "5"
|
||||
|
||||
# UI framework
|
||||
iced = { version = "0.13", features = ["wgpu", "advanced", "image", "tokio"] }
|
||||
iced = { version = "0.13", features = ["wgpu", "advanced", "image", "svg", "tokio"] }
|
||||
|
||||
# Editor widget
|
||||
cosmic-text = "0.12"
|
||||
|
||||
6
crates/bds-ui/assets/icons/chat.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"/>
|
||||
<circle cx="8" cy="10" r="1.5"/>
|
||||
<circle cx="12" cy="10" r="1.5"/>
|
||||
<circle cx="16" cy="10" r="1.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
3
crates/bds-ui/assets/icons/git.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M22 11.73L12.27 2a1 1 0 0 0-1.41 0L8.84 4.02l2.56 2.56a1.2 1.2 0 0 1 1.52 1.53l2.47 2.47a1.2 1.2 0 1 1-.72.67l-2.3-2.3v6.06a1.2 1.2 0 1 1-.85 0V8.9a1.2 1.2 0 0 1-.66-1.59L8.35 4.8 2 11.16a1 1 0 0 0 0 1.41L11.73 22a1 1 0 0 0 1.41 0L22 13.14a1 1 0 0 0 0-1.41z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 378 B |
3
crates/bds-ui/assets/icons/import.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 162 B |
3
crates/bds-ui/assets/icons/media.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
3
crates/bds-ui/assets/icons/pages.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M4 4h10v4h6v12H4V4zm10 1.5V9h4.5L14 5.5zM7 12h10v1.5H7V12zm0 3h10v1.5H7V15z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 196 B |
4
crates/bds-ui/assets/icons/posts.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"/>
|
||||
<path d="M8 12h8v2H8zm0 4h8v2H8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
3
crates/bds-ui/assets/icons/scripts.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M20 3H4a1 1 0 0 0-1 1v11a1 1 0 0 0 1 1h7v2H8v2h8v-2h-3v-2h7a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1zM5 14V5h14v9H5zm2-7.5L9.5 9 7 11.5l1.4 1.4L12.3 9 8.4 5.1 7 6.5zm6.5 5.5h4v-2h-4v2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
3
crates/bds-ui/assets/icons/settings.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 822 B |
3
crates/bds-ui/assets/icons/tags.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58s1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41s-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 360 B |
3
crates/bds-ui/assets/icons/templates.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="#ffffff">
|
||||
<path d="M4 4h7v7H4V4zm9 0h7v7h-7V4zM4 13h7v7H4v-7zm9 0h7v7h-7v-7zM5.5 5.5v4h4v-4h-4zm9 0v4h4v-4h-4zm-9 9v4h4v-4h-4zm9 0v4h4v-4h-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -3,6 +3,7 @@ use bds_ui::BdsApp;
|
||||
fn main() -> iced::Result {
|
||||
iced::application("bDS", BdsApp::update, BdsApp::view)
|
||||
.subscription(BdsApp::subscription)
|
||||
.theme(|_| iced::Theme::Dark)
|
||||
.window_size((1200.0, 800.0))
|
||||
.run_with(BdsApp::new)
|
||||
}
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
use iced::widget::{button, column, container, text, Column, Space};
|
||||
use iced::{Alignment, Element, Length};
|
||||
use iced::widget::{button, column, container, svg, Column, Space};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::state::navigation::SidebarView;
|
||||
|
||||
/// Activity-bar short labels used in M2 (no SVG icons yet).
|
||||
fn short_label(view: SidebarView) -> &'static str {
|
||||
// ---------------------------------------------------------------------------
|
||||
// SVG icon data — ported from bDS ActivityBar.tsx inline SVGs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn icon_svg(view: SidebarView) -> &'static [u8] {
|
||||
match view {
|
||||
SidebarView::Posts => "Po",
|
||||
SidebarView::Pages => "Pa",
|
||||
SidebarView::Media => "Me",
|
||||
SidebarView::Scripts => "Sc",
|
||||
SidebarView::Templates => "Tp",
|
||||
SidebarView::Tags => "Ta",
|
||||
SidebarView::Chat => "AI",
|
||||
SidebarView::Import => "Im",
|
||||
SidebarView::Git => "Gi",
|
||||
SidebarView::Settings => "Se",
|
||||
SidebarView::Posts => include_bytes!("../../assets/icons/posts.svg"),
|
||||
SidebarView::Pages => include_bytes!("../../assets/icons/pages.svg"),
|
||||
SidebarView::Media => include_bytes!("../../assets/icons/media.svg"),
|
||||
SidebarView::Scripts => include_bytes!("../../assets/icons/scripts.svg"),
|
||||
SidebarView::Templates => include_bytes!("../../assets/icons/templates.svg"),
|
||||
SidebarView::Tags => include_bytes!("../../assets/icons/tags.svg"),
|
||||
SidebarView::Chat => include_bytes!("../../assets/icons/chat.svg"),
|
||||
SidebarView::Import => include_bytes!("../../assets/icons/import.svg"),
|
||||
SidebarView::Git => include_bytes!("../../assets/icons/git.svg"),
|
||||
SidebarView::Settings => include_bytes!("../../assets/icons/settings.svg"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,28 +43,93 @@ const BOTTOM_ACTIVITIES: &[SidebarView] = &[
|
||||
SidebarView::Settings,
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles — matching bDS ActivityBar.css
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Active button: transparent bg.
|
||||
fn active_button_style(_theme: &Theme, _status: button::Status) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Inactive button: hover brightens background.
|
||||
fn inactive_button_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.22, 0.22, 0.27),
|
||||
_ => Color::TRANSPARENT,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb(0.55, 0.55, 0.60),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Activity bar container: dark background.
|
||||
fn bar_background_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.14, 0.14, 0.18))),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// View
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn view(
|
||||
active_view: SidebarView,
|
||||
_locale: UiLocale,
|
||||
) -> Element<'static, Message> {
|
||||
let make_btn = |view: SidebarView| -> Element<'static, Message> {
|
||||
let label = short_label(view);
|
||||
let mut btn = button(
|
||||
text(label).size(12).center(),
|
||||
let handle = svg::Handle::from_memory(icon_svg(view));
|
||||
let is_active = view == active_view;
|
||||
|
||||
// Render SVG: active = full opacity, inactive = 0.4 opacity (like bDS 60% opacity)
|
||||
let icon = svg(handle)
|
||||
.width(Length::Fixed(24.0))
|
||||
.height(Length::Fixed(24.0))
|
||||
.opacity(if is_active { 1.0 } else { 0.4 });
|
||||
|
||||
let btn = button(
|
||||
container(icon)
|
||||
.center_x(Length::Fixed(48.0))
|
||||
.center_y(Length::Fixed(48.0)),
|
||||
)
|
||||
.width(Length::Fixed(48.0))
|
||||
.height(Length::Fixed(48.0))
|
||||
.on_press(Message::SetActiveView(view));
|
||||
.padding(0)
|
||||
.on_press(Message::SetActiveView(view))
|
||||
.style(if is_active { active_button_style } else { inactive_button_style });
|
||||
|
||||
if view == active_view {
|
||||
btn = btn.style(button::primary);
|
||||
// Active indicator: 2px left border (like bDS/VS Code)
|
||||
if is_active {
|
||||
let indicator = container(Space::new(0, 0))
|
||||
.width(Length::Fixed(2.0))
|
||||
.height(Length::Fixed(48.0))
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::WHITE)),
|
||||
..container::Style::default()
|
||||
});
|
||||
iced::widget::row![indicator, btn].into()
|
||||
} else {
|
||||
btn = btn.style(button::secondary);
|
||||
let spacer = Space::with_width(2.0);
|
||||
iced::widget::row![spacer, btn].into()
|
||||
}
|
||||
|
||||
container(btn)
|
||||
.center_x(Length::Fixed(48.0))
|
||||
.into()
|
||||
};
|
||||
|
||||
let top_items: Vec<Element<'static, Message>> = TOP_ACTIVITIES
|
||||
@@ -74,16 +142,21 @@ pub fn view(
|
||||
.map(|v| make_btn(*v))
|
||||
.collect();
|
||||
|
||||
let top = Column::with_children(top_items).spacing(2);
|
||||
let bottom = Column::with_children(bottom_items).spacing(2);
|
||||
let top = Column::with_children(top_items).spacing(0);
|
||||
let bottom = Column::with_children(bottom_items).spacing(0);
|
||||
|
||||
column![
|
||||
top,
|
||||
Space::with_height(Length::Fill),
|
||||
bottom,
|
||||
]
|
||||
.width(Length::Fixed(48.0))
|
||||
container(
|
||||
column![
|
||||
top,
|
||||
Space::with_height(Length::Fill),
|
||||
bottom,
|
||||
]
|
||||
.width(Length::Fixed(50.0))
|
||||
.height(Length::Fill)
|
||||
.padding([4, 0]),
|
||||
)
|
||||
.width(Length::Fixed(50.0))
|
||||
.height(Length::Fill)
|
||||
.align_x(Alignment::Center)
|
||||
.style(bar_background_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use iced::widget::{button, column, container, row, scrollable, text, Space};
|
||||
use iced::{Alignment, Element, Length};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
@@ -7,41 +7,83 @@ use crate::app::Message;
|
||||
use crate::i18n::t;
|
||||
use crate::state::navigation::{OutputEntry, PanelTab, TaskSnapshot};
|
||||
|
||||
/// Panel background style.
|
||||
fn panel_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.13, 0.13, 0.16))),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Panel tab button — active.
|
||||
fn tab_active(_theme: &Theme, _status: button::Status) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.20, 0.20, 0.25))),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.30, 0.55, 0.90),
|
||||
width: 0.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Panel tab button — inactive.
|
||||
fn tab_inactive(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.18, 0.18, 0.22),
|
||||
_ => Color::TRANSPARENT,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb(0.60, 0.60, 0.65),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Close button style.
|
||||
fn close_btn_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let color = match status {
|
||||
button::Status::Hovered => Color::WHITE,
|
||||
_ => Color::from_rgb(0.50, 0.50, 0.55),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: color,
|
||||
border: Border::default(),
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(
|
||||
panel_tab: PanelTab,
|
||||
task_snapshots: &[TaskSnapshot],
|
||||
output_entries: &[OutputEntry],
|
||||
locale: UiLocale,
|
||||
) -> Element<'static, Message> {
|
||||
let muted = Color::from_rgb(0.50, 0.50, 0.55);
|
||||
|
||||
// Tab header
|
||||
let tasks_btn = {
|
||||
let mut btn = button(text(t(locale, "common.tasks")).size(12))
|
||||
.on_press(Message::SetPanelTab(PanelTab::Tasks))
|
||||
.padding([4, 8]);
|
||||
if panel_tab == PanelTab::Tasks {
|
||||
btn = btn.style(button::primary);
|
||||
} else {
|
||||
btn = btn.style(button::secondary);
|
||||
}
|
||||
btn
|
||||
};
|
||||
let tasks_btn = button(text(t(locale, "common.tasks")).size(12))
|
||||
.on_press(Message::SetPanelTab(PanelTab::Tasks))
|
||||
.padding([4, 8])
|
||||
.style(if panel_tab == PanelTab::Tasks { tab_active } else { tab_inactive });
|
||||
|
||||
let output_btn = {
|
||||
let mut btn = button(text(t(locale, "panel.output")).size(12))
|
||||
.on_press(Message::SetPanelTab(PanelTab::Output))
|
||||
.padding([4, 8]);
|
||||
if panel_tab == PanelTab::Output {
|
||||
btn = btn.style(button::primary);
|
||||
} else {
|
||||
btn = btn.style(button::secondary);
|
||||
}
|
||||
btn
|
||||
};
|
||||
let output_btn = button(text(t(locale, "panel.output")).size(12))
|
||||
.on_press(Message::SetPanelTab(PanelTab::Output))
|
||||
.padding([4, 8])
|
||||
.style(if panel_tab == PanelTab::Output { tab_active } else { tab_inactive });
|
||||
|
||||
let close_btn = button(text("×").size(12))
|
||||
let close_btn = button(text("\u{2715}").size(12))
|
||||
.on_press(Message::TogglePanel)
|
||||
.padding([4, 6])
|
||||
.style(button::text);
|
||||
.style(close_btn_style);
|
||||
|
||||
let tab_header = row![
|
||||
tasks_btn,
|
||||
@@ -50,13 +92,14 @@ pub fn view(
|
||||
close_btn,
|
||||
]
|
||||
.spacing(4)
|
||||
.align_y(Alignment::Center);
|
||||
.align_y(Alignment::Center)
|
||||
.padding([4, 8]);
|
||||
|
||||
// Tab content
|
||||
let content: Element<'static, Message> = match panel_tab {
|
||||
PanelTab::Tasks => {
|
||||
if task_snapshots.is_empty() {
|
||||
container(text(t(locale, "tasks.noActive")).size(12))
|
||||
container(text(t(locale, "tasks.noActive")).size(12).color(muted))
|
||||
.padding(8)
|
||||
.into()
|
||||
} else {
|
||||
@@ -64,14 +107,14 @@ pub fn view(
|
||||
.iter()
|
||||
.map(|snap| {
|
||||
let status_text = format!(
|
||||
"{} — {}{}",
|
||||
"{} \u{2014} {}{}",
|
||||
snap.label,
|
||||
snap.status,
|
||||
snap.progress
|
||||
.map(|p| format!(" ({:.0}%)", p * 100.0))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
text(status_text).size(11).into()
|
||||
text(status_text).size(11).color(Color::from_rgb(0.70, 0.70, 0.75)).into()
|
||||
})
|
||||
.collect();
|
||||
scrollable(
|
||||
@@ -84,13 +127,18 @@ pub fn view(
|
||||
}
|
||||
PanelTab::Output => {
|
||||
if output_entries.is_empty() {
|
||||
container(text(t(locale, "panel.noOutput")).size(12))
|
||||
container(text(t(locale, "panel.noOutput")).size(12).color(muted))
|
||||
.padding(8)
|
||||
.into()
|
||||
} else {
|
||||
let items: Vec<Element<'static, Message>> = output_entries
|
||||
.iter()
|
||||
.map(|entry| text(entry.text.clone()).size(11).into())
|
||||
.map(|entry| {
|
||||
text(entry.text.clone())
|
||||
.size(11)
|
||||
.color(Color::from_rgb(0.70, 0.70, 0.75))
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
scrollable(
|
||||
iced::widget::Column::with_children(items)
|
||||
@@ -103,9 +151,10 @@ pub fn view(
|
||||
};
|
||||
|
||||
container(
|
||||
column![tab_header, content].spacing(4),
|
||||
column![tab_header, content].spacing(0),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(200.0))
|
||||
.style(panel_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use iced::widget::{column, container, scrollable, text};
|
||||
use iced::{Element, Length};
|
||||
use iced::widget::{column, container, scrollable, text, Space};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
@@ -7,31 +7,57 @@ use crate::app::Message;
|
||||
use crate::i18n::t;
|
||||
use crate::state::navigation::SidebarView;
|
||||
|
||||
/// Sidebar container style — dark background with right border separator.
|
||||
fn sidebar_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.16, 0.16, 0.20))),
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.25, 0.25, 0.30),
|
||||
width: 1.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the appropriate empty-state message key for each sidebar view.
|
||||
fn placeholder_key(view: SidebarView) -> &'static str {
|
||||
match view {
|
||||
SidebarView::Posts => "sidebar.noPostsYet",
|
||||
SidebarView::Pages => "sidebar.noPagesYet",
|
||||
SidebarView::Media => "sidebar.noMediaYet",
|
||||
SidebarView::Scripts => "sidebar.noScriptsYet",
|
||||
SidebarView::Templates => "sidebar.noTemplatesYet",
|
||||
SidebarView::Tags => "sidebar.tagsHeader",
|
||||
SidebarView::Chat => "sidebar.chatPlaceholder",
|
||||
SidebarView::Import => "sidebar.importPlaceholder",
|
||||
SidebarView::Git => "sidebar.gitPlaceholder",
|
||||
SidebarView::Settings => "sidebar.settingsHeader",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(
|
||||
sidebar_view: SidebarView,
|
||||
locale: UiLocale,
|
||||
) -> Element<'static, Message> {
|
||||
let header_key = sidebar_view.i18n_key();
|
||||
let header_text = t(locale, header_key);
|
||||
|
||||
let placeholder = match sidebar_view {
|
||||
SidebarView::Posts => t(locale, "sidebar.noPostsYet"),
|
||||
SidebarView::Pages => t(locale, "sidebar.noPagesYet"),
|
||||
SidebarView::Media => t(locale, "sidebar.noMediaYet"),
|
||||
SidebarView::Settings => t(locale, "sidebar.settingsHeader"),
|
||||
SidebarView::Tags => t(locale, "sidebar.tagsHeader"),
|
||||
_ => t(locale, "sidebar.loading"),
|
||||
};
|
||||
let header_text = t(locale, sidebar_view.i18n_key());
|
||||
let placeholder = t(locale, placeholder_key(sidebar_view));
|
||||
|
||||
let content = column![
|
||||
text(header_text).size(14),
|
||||
text(placeholder).size(12),
|
||||
text(header_text)
|
||||
.size(13)
|
||||
.color(Color::from_rgb(0.85, 0.85, 0.90)),
|
||||
Space::with_height(8.0),
|
||||
text(placeholder)
|
||||
.size(12)
|
||||
.color(Color::from_rgb(0.50, 0.50, 0.55)),
|
||||
]
|
||||
.spacing(8)
|
||||
.spacing(4)
|
||||
.padding(12);
|
||||
|
||||
container(scrollable(content))
|
||||
.width(Length::Fixed(280.0))
|
||||
.width(Length::Fixed(240.0))
|
||||
.height(Length::Fill)
|
||||
.style(sidebar_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use iced::widget::{button, container, row, text, Space};
|
||||
use iced::{Alignment, Element, Length};
|
||||
use iced::widget::{button, container, pick_list, row, text, Space};
|
||||
use iced::{Alignment, Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
@@ -7,6 +7,110 @@ use crate::app::Message;
|
||||
use crate::i18n::{t, tw};
|
||||
use crate::state::navigation::TaskSnapshot;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Locale option wrapper for pick_list (needs ToString + PartialEq + Clone)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Wraps a UiLocale with a display label for the pick_list dropdown.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct LocaleOption {
|
||||
locale: UiLocale,
|
||||
label: String,
|
||||
}
|
||||
|
||||
impl ToString for LocaleOption {
|
||||
fn to_string(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl LocaleOption {
|
||||
fn new(locale: UiLocale, display_locale: UiLocale) -> Self {
|
||||
let key = match locale {
|
||||
UiLocale::En => "language.en",
|
||||
UiLocale::De => "language.de",
|
||||
UiLocale::Fr => "language.fr",
|
||||
UiLocale::It => "language.it",
|
||||
UiLocale::Es => "language.es",
|
||||
};
|
||||
Self {
|
||||
locale,
|
||||
label: t(display_locale, key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Styles
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Status bar background.
|
||||
fn bar_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.12, 0.12, 0.16))),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Airplane button — inactive (dim).
|
||||
fn airplane_inactive(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.22, 0.22, 0.27),
|
||||
_ => Color::TRANSPARENT,
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgba(0.70, 0.70, 0.75, 0.4),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Airplane button — active (warning color).
|
||||
fn airplane_active(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.85, 0.65, 0.10),
|
||||
_ => Color::from_rgb(0.75, 0.55, 0.05),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Pick list style matching status bar.
|
||||
fn locale_pick_list_style(_theme: &Theme, status: pick_list::Status) -> pick_list::Style {
|
||||
let bg = match status {
|
||||
pick_list::Status::Hovered => Color::from_rgb(0.22, 0.22, 0.27),
|
||||
_ => Color::TRANSPARENT,
|
||||
};
|
||||
pick_list::Style {
|
||||
text_color: Color::from_rgb(0.70, 0.70, 0.75),
|
||||
placeholder_color: Color::from_rgb(0.50, 0.50, 0.55),
|
||||
handle_color: Color::from_rgb(0.50, 0.50, 0.55),
|
||||
background: Background::Color(bg),
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.25, 0.25, 0.30),
|
||||
width: 1.0,
|
||||
radius: 3.0.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// View
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn view(
|
||||
active_project_name: Option<&str>,
|
||||
post_count: usize,
|
||||
@@ -15,9 +119,11 @@ pub fn view(
|
||||
offline_mode: bool,
|
||||
task_snapshots: &[TaskSnapshot],
|
||||
) -> Element<'static, Message> {
|
||||
// Left: project name + task indicator
|
||||
let project_label = active_project_name.unwrap_or("—").to_string();
|
||||
let project_text = text(project_label).size(12);
|
||||
let label_color = Color::from_rgb(0.60, 0.60, 0.65);
|
||||
|
||||
// ── Left: project name + task indicator ──
|
||||
let project_label = active_project_name.unwrap_or("\u{2014}").to_string();
|
||||
let project_text = text(project_label).size(12).color(Color::from_rgb(0.80, 0.80, 0.85));
|
||||
|
||||
let running: Vec<&TaskSnapshot> = task_snapshots
|
||||
.iter()
|
||||
@@ -26,9 +132,9 @@ pub fn view(
|
||||
let task_indicator: Element<'static, Message> = if !running.is_empty() {
|
||||
let first = running[0].label.clone();
|
||||
if running.len() > 1 {
|
||||
text(format!("{first} (+{})", running.len() - 1)).size(11).into()
|
||||
text(format!("{first} (+{})", running.len() - 1)).size(11).color(label_color).into()
|
||||
} else {
|
||||
text(first).size(11).into()
|
||||
text(first).size(11).color(label_color).into()
|
||||
}
|
||||
} else {
|
||||
Space::with_width(0).into()
|
||||
@@ -36,50 +142,44 @@ pub fn view(
|
||||
|
||||
let left = row![project_text, task_indicator].spacing(12);
|
||||
|
||||
// Right: post count, media count, airplane mode, locale selector, brand
|
||||
// ── Right side ──
|
||||
|
||||
// Post + media counts
|
||||
let posts_label = tw(locale, "statusBar.posts", &[("count", &post_count.to_string())]);
|
||||
let media_label = tw(locale, "statusBar.media", &[("count", &media_count.to_string())]);
|
||||
|
||||
let airplane_label = if offline_mode {
|
||||
t(locale, "statusBar.offlineModeActive")
|
||||
} else {
|
||||
t(locale, "statusBar.offlineMode")
|
||||
};
|
||||
|
||||
let airplane_btn = button(text(airplane_label).size(11))
|
||||
// Airplane mode toggle — just the ✈ icon, like bDS
|
||||
let airplane_btn = button(text("\u{2708}").size(13))
|
||||
.on_press(Message::SetOfflineMode(!offline_mode))
|
||||
.padding([2, 6])
|
||||
.style(if offline_mode { button::primary } else { button::text });
|
||||
.padding([2, 4])
|
||||
.style(if offline_mode { airplane_active } else { airplane_inactive });
|
||||
|
||||
let ui_label = t(locale, "statusBar.ui");
|
||||
|
||||
let locale_buttons: Vec<Element<'static, Message>> = UiLocale::all()
|
||||
// Language selector — "UI" label + pick_list dropdown, like bDS
|
||||
let options: Vec<LocaleOption> = UiLocale::all()
|
||||
.iter()
|
||||
.map(|l| {
|
||||
let code = l.code().to_string();
|
||||
let mut btn = button(text(code).size(11))
|
||||
.on_press(Message::SetUiLocale(*l))
|
||||
.padding([2, 4]);
|
||||
if *l == locale {
|
||||
btn = btn.style(button::primary);
|
||||
} else {
|
||||
btn = btn.style(button::text);
|
||||
}
|
||||
btn.into()
|
||||
})
|
||||
.map(|l| LocaleOption::new(*l, locale))
|
||||
.collect();
|
||||
let selected = Some(LocaleOption::new(locale, locale));
|
||||
|
||||
let locale_row = iced::widget::Row::with_children(locale_buttons).spacing(2);
|
||||
let locale_picker = pick_list(
|
||||
options,
|
||||
selected,
|
||||
|opt: LocaleOption| Message::SetUiLocale(opt.locale),
|
||||
)
|
||||
.text_size(11)
|
||||
.padding([2, 4])
|
||||
.width(Length::Shrink)
|
||||
.style(locale_pick_list_style);
|
||||
|
||||
let right = row![
|
||||
text(posts_label).size(11),
|
||||
text(media_label).size(11),
|
||||
text(posts_label).size(11).color(label_color),
|
||||
text(media_label).size(11).color(label_color),
|
||||
airplane_btn,
|
||||
text(ui_label).size(11),
|
||||
locale_row,
|
||||
text("bDS").size(11),
|
||||
text(t(locale, "statusBar.ui")).size(11).color(label_color),
|
||||
locale_picker,
|
||||
text("bDS").size(11).color(Color::from_rgb(0.45, 0.45, 0.50)),
|
||||
]
|
||||
.spacing(12)
|
||||
.spacing(8)
|
||||
.align_y(Alignment::Center);
|
||||
|
||||
container(
|
||||
@@ -92,6 +192,7 @@ pub fn view(
|
||||
.padding([0, 8]),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(22.0))
|
||||
.height(Length::Fixed(24.0))
|
||||
.style(bar_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,15 +1,78 @@
|
||||
use iced::widget::{button, container, row, text, Space};
|
||||
use iced::{Element, Length};
|
||||
use iced::{Background, Border, Color, Element, Length, Theme};
|
||||
|
||||
use crate::app::Message;
|
||||
use crate::state::tabs::Tab;
|
||||
|
||||
/// Tab bar background.
|
||||
fn bar_style(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.14, 0.14, 0.18))),
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.25, 0.25, 0.30),
|
||||
width: 0.0,
|
||||
radius: 0.0.into(),
|
||||
},
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Active tab style.
|
||||
fn tab_active(_theme: &Theme, _status: button::Status) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))),
|
||||
text_color: Color::WHITE,
|
||||
border: Border {
|
||||
color: Color::from_rgb(0.30, 0.55, 0.90),
|
||||
width: 0.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Inactive tab style.
|
||||
fn tab_inactive(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let bg = match status {
|
||||
button::Status::Hovered => Color::from_rgb(0.18, 0.18, 0.22),
|
||||
_ => Color::from_rgb(0.14, 0.14, 0.18),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(bg)),
|
||||
text_color: Color::from_rgb(0.60, 0.60, 0.65),
|
||||
border: Border {
|
||||
color: Color::TRANSPARENT,
|
||||
width: 0.0,
|
||||
radius: 4.0.into(),
|
||||
},
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Close button on tab.
|
||||
fn close_style(_theme: &Theme, status: button::Status) -> button::Style {
|
||||
let color = match status {
|
||||
button::Status::Hovered => Color::WHITE,
|
||||
_ => Color::from_rgb(0.45, 0.45, 0.50),
|
||||
};
|
||||
button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: color,
|
||||
border: Border::default(),
|
||||
..button::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(
|
||||
tabs: &[Tab],
|
||||
active_tab: Option<&str>,
|
||||
) -> Element<'static, Message> {
|
||||
if tabs.is_empty() {
|
||||
return Space::with_height(0).into();
|
||||
return container(Space::new(0, 0))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(35.0))
|
||||
.style(bar_style)
|
||||
.into();
|
||||
}
|
||||
|
||||
let tab_buttons: Vec<Element<'static, Message>> = tabs
|
||||
@@ -17,7 +80,7 @@ pub fn view(
|
||||
.map(|tab| {
|
||||
let is_active = active_tab == Some(tab.id.as_str());
|
||||
let title_text = if tab.is_transient {
|
||||
format!("{} (preview)", tab.title)
|
||||
format!("{} \u{25CB}", tab.title) // hollow circle for transient
|
||||
} else {
|
||||
tab.title.clone()
|
||||
};
|
||||
@@ -26,24 +89,19 @@ pub fn view(
|
||||
|
||||
let label = row![
|
||||
text(title_text).size(12),
|
||||
button(text("×").size(12))
|
||||
button(text("\u{2715}").size(10))
|
||||
.on_press(Message::CloseTab(close_id))
|
||||
.padding(2)
|
||||
.style(button::text),
|
||||
.style(close_style),
|
||||
]
|
||||
.spacing(4);
|
||||
.spacing(6)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
||||
let mut btn = button(label)
|
||||
button(label)
|
||||
.on_press(Message::SelectTab(tab_id))
|
||||
.padding([4, 8]);
|
||||
|
||||
if is_active {
|
||||
btn = btn.style(button::primary);
|
||||
} else {
|
||||
btn = btn.style(button::secondary);
|
||||
}
|
||||
|
||||
btn.into()
|
||||
.padding([6, 12])
|
||||
.style(if is_active { tab_active } else { tab_inactive })
|
||||
.into()
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -54,5 +112,6 @@ pub fn view(
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(35.0))
|
||||
.style(bar_style)
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use iced::widget::{column, container, text};
|
||||
use iced::{Element, Length};
|
||||
use iced::{Background, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
@@ -12,8 +12,12 @@ pub fn view(locale: UiLocale) -> Element<'static, Message> {
|
||||
|
||||
container(
|
||||
column![
|
||||
text(title).size(28),
|
||||
text(subtitle).size(14),
|
||||
text(title)
|
||||
.size(28)
|
||||
.color(Color::from_rgb(0.85, 0.85, 0.90)),
|
||||
text(subtitle)
|
||||
.size(14)
|
||||
.color(Color::from_rgb(0.55, 0.55, 0.60)),
|
||||
]
|
||||
.spacing(12)
|
||||
.align_x(iced::Alignment::Center),
|
||||
@@ -22,5 +26,9 @@ pub fn view(locale: UiLocale) -> Element<'static, Message> {
|
||||
.height(Length::Fill)
|
||||
.center_x(Length::Fill)
|
||||
.center_y(Length::Fill)
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))),
|
||||
..container::Style::default()
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use iced::widget::{column, row};
|
||||
use iced::{Element, Length};
|
||||
use iced::widget::{column, container, row, Space};
|
||||
use iced::{Background, Color, Element, Length, Theme};
|
||||
|
||||
use bds_core::i18n::UiLocale;
|
||||
|
||||
@@ -8,6 +8,36 @@ use crate::state::navigation::{OutputEntry, PanelTab, SidebarView, TaskSnapshot}
|
||||
use crate::state::tabs::Tab;
|
||||
use crate::views::{activity_bar, panel, sidebar, status_bar, tab_bar, welcome};
|
||||
|
||||
/// Main content area background.
|
||||
fn content_bg(_theme: &Theme) -> container::Style {
|
||||
container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.11, 0.11, 0.14))),
|
||||
..container::Style::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Horizontal separator line between regions.
|
||||
fn separator_v() -> iced::widget::Container<'static, Message> {
|
||||
container(Space::new(0, 0))
|
||||
.width(Length::Fixed(1.0))
|
||||
.height(Length::Fill)
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.25, 0.25, 0.30))),
|
||||
..container::Style::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Horizontal line separator (full width).
|
||||
fn separator_h() -> iced::widget::Container<'static, Message> {
|
||||
container(Space::new(0, 0))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fixed(1.0))
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.25, 0.25, 0.30))),
|
||||
..container::Style::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Compose the full workspace layout.
|
||||
pub fn view(
|
||||
// Navigation
|
||||
@@ -32,38 +62,31 @@ pub fn view(
|
||||
// Activity bar (leftmost column)
|
||||
let activity = activity_bar::view(sidebar_view, locale);
|
||||
|
||||
// Sidebar (conditionally visible)
|
||||
let sidebar_el: Option<Element<'static, Message>> = if sidebar_visible {
|
||||
Some(sidebar::view(sidebar_view, locale))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Tab bar
|
||||
let tabs_el = tab_bar::view(tabs, active_tab);
|
||||
|
||||
// Content area
|
||||
let content_area = welcome::view(locale);
|
||||
|
||||
// Panel (conditionally visible)
|
||||
let panel_el: Option<Element<'static, Message>> = if panel_visible {
|
||||
Some(panel::view(panel_tab, task_snapshots, output_entries, locale))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Right column: tab bar + content + panel
|
||||
let mut right_col = column![tabs_el, content_area];
|
||||
if let Some(p) = panel_el {
|
||||
right_col = right_col.push(p);
|
||||
if panel_visible {
|
||||
right_col = right_col.push(separator_h());
|
||||
right_col = right_col.push(panel::view(panel_tab, task_snapshots, output_entries, locale));
|
||||
}
|
||||
let right = right_col.width(Length::Fill).height(Length::Fill);
|
||||
let right = container(right_col.width(Length::Fill).height(Length::Fill))
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.style(content_bg);
|
||||
|
||||
// Main row: activity bar + sidebar? + right column
|
||||
// Main row: activity bar | separator | sidebar? | separator | right column
|
||||
let mut main_row = row![activity];
|
||||
if let Some(sb) = sidebar_el {
|
||||
main_row = main_row.push(sb);
|
||||
|
||||
if sidebar_visible {
|
||||
main_row = main_row.push(sidebar::view(sidebar_view, locale));
|
||||
main_row = main_row.push(separator_v());
|
||||
}
|
||||
|
||||
main_row = main_row.push(right);
|
||||
let main_row = main_row.height(Length::Fill);
|
||||
|
||||
@@ -77,7 +100,7 @@ pub fn view(
|
||||
task_snapshots,
|
||||
);
|
||||
|
||||
column![main_row, status]
|
||||
column![main_row, separator_h(), status]
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
|
||||
@@ -38,9 +38,10 @@
|
||||
"activity.scripts": "Skripte",
|
||||
"activity.tags": "Schlagwörter",
|
||||
"activity.templates": "Vorlagen",
|
||||
"activity.aiAssistant": "KI-Assistent",
|
||||
"activity.chat": "KI-Assistent",
|
||||
"activity.import": "Importieren",
|
||||
"activity.sourceControl": "Versionskontrolle",
|
||||
"activity.git": "Versionskontrolle",
|
||||
"activity.settings": "Einstellungen",
|
||||
"common.save": "Speichern",
|
||||
"common.cancel": "Abbrechen",
|
||||
"common.settings": "Einstellungen",
|
||||
@@ -85,6 +86,11 @@
|
||||
"sidebar.noPostsYet": "Noch keine Beiträge",
|
||||
"sidebar.noPagesYet": "Noch keine Seiten",
|
||||
"sidebar.noMediaYet": "Noch keine Medien",
|
||||
"sidebar.noScriptsYet": "Noch keine Skripte",
|
||||
"sidebar.noTemplatesYet": "Noch keine Vorlagen",
|
||||
"sidebar.chatPlaceholder": "KI-Assistent erscheint hier",
|
||||
"sidebar.importPlaceholder": "Inhalte aus externen Quellen importieren",
|
||||
"sidebar.gitPlaceholder": "Versionskontrolle-Integration",
|
||||
"sidebar.loading": "Lädt...",
|
||||
"sidebar.settingsHeader": "Einstellungen",
|
||||
"sidebar.tagsHeader": "Schlagwörter",
|
||||
|
||||
@@ -38,9 +38,10 @@
|
||||
"activity.scripts": "Scripts",
|
||||
"activity.tags": "Tags",
|
||||
"activity.templates": "Templates",
|
||||
"activity.aiAssistant": "AI Assistant",
|
||||
"activity.chat": "AI Assistant",
|
||||
"activity.import": "Import",
|
||||
"activity.sourceControl": "Source Control",
|
||||
"activity.git": "Source Control",
|
||||
"activity.settings": "Settings",
|
||||
"common.save": "Save",
|
||||
"common.cancel": "Cancel",
|
||||
"common.settings": "Settings",
|
||||
@@ -85,6 +86,11 @@
|
||||
"sidebar.noPostsYet": "No posts yet",
|
||||
"sidebar.noPagesYet": "No pages yet",
|
||||
"sidebar.noMediaYet": "No media yet",
|
||||
"sidebar.noScriptsYet": "No scripts yet",
|
||||
"sidebar.noTemplatesYet": "No templates yet",
|
||||
"sidebar.chatPlaceholder": "AI assistant will appear here",
|
||||
"sidebar.importPlaceholder": "Import content from external sources",
|
||||
"sidebar.gitPlaceholder": "Source control integration",
|
||||
"sidebar.loading": "Loading...",
|
||||
"sidebar.settingsHeader": "Settings",
|
||||
"sidebar.tagsHeader": "Tags",
|
||||
|
||||
@@ -38,9 +38,10 @@
|
||||
"activity.scripts": "Scripts",
|
||||
"activity.tags": "Etiquetas",
|
||||
"activity.templates": "Plantillas",
|
||||
"activity.aiAssistant": "Asistente IA",
|
||||
"activity.chat": "Asistente IA",
|
||||
"activity.import": "Importar",
|
||||
"activity.sourceControl": "Control de código fuente",
|
||||
"activity.git": "Control de código fuente",
|
||||
"activity.settings": "Configuración",
|
||||
"common.save": "Guardar",
|
||||
"common.cancel": "Cancelar",
|
||||
"common.settings": "Configuración",
|
||||
@@ -85,6 +86,11 @@
|
||||
"sidebar.noPostsYet": "Aún no hay entradas",
|
||||
"sidebar.noPagesYet": "Aún no hay páginas",
|
||||
"sidebar.noMediaYet": "Aún no hay medios",
|
||||
"sidebar.noScriptsYet": "Aún no hay scripts",
|
||||
"sidebar.noTemplatesYet": "Aún no hay plantillas",
|
||||
"sidebar.chatPlaceholder": "El asistente IA aparecerá aquí",
|
||||
"sidebar.importPlaceholder": "Importar contenido desde fuentes externas",
|
||||
"sidebar.gitPlaceholder": "Integración del control de código fuente",
|
||||
"sidebar.loading": "Cargando...",
|
||||
"sidebar.settingsHeader": "Configuración",
|
||||
"sidebar.tagsHeader": "Etiquetas",
|
||||
|
||||
@@ -38,9 +38,10 @@
|
||||
"activity.scripts": "Scripts",
|
||||
"activity.tags": "Étiquettes",
|
||||
"activity.templates": "Modèles",
|
||||
"activity.aiAssistant": "Assistant IA",
|
||||
"activity.chat": "Assistant IA",
|
||||
"activity.import": "Importation",
|
||||
"activity.sourceControl": "Contrôle de source",
|
||||
"activity.git": "Contrôle de source",
|
||||
"activity.settings": "Paramètres",
|
||||
"common.save": "Enregistrer",
|
||||
"common.cancel": "Annuler",
|
||||
"common.settings": "Paramètres",
|
||||
@@ -85,6 +86,11 @@
|
||||
"sidebar.noPostsYet": "Aucun article pour le moment",
|
||||
"sidebar.noPagesYet": "Aucune page pour le moment",
|
||||
"sidebar.noMediaYet": "Aucun média pour le moment",
|
||||
"sidebar.noScriptsYet": "Aucun script",
|
||||
"sidebar.noTemplatesYet": "Aucun modèle",
|
||||
"sidebar.chatPlaceholder": "L'assistant IA apparaîtra ici",
|
||||
"sidebar.importPlaceholder": "Importer du contenu depuis des sources externes",
|
||||
"sidebar.gitPlaceholder": "Intégration du contrôle de source",
|
||||
"sidebar.loading": "Chargement...",
|
||||
"sidebar.settingsHeader": "Paramètres",
|
||||
"sidebar.tagsHeader": "Étiquettes",
|
||||
|
||||
@@ -38,9 +38,10 @@
|
||||
"activity.scripts": "Script",
|
||||
"activity.tags": "Tag",
|
||||
"activity.templates": "Modelli",
|
||||
"activity.aiAssistant": "Assistente IA",
|
||||
"activity.chat": "Assistente IA",
|
||||
"activity.import": "Importa",
|
||||
"activity.sourceControl": "Controllo sorgente",
|
||||
"activity.git": "Controllo sorgente",
|
||||
"activity.settings": "Impostazioni",
|
||||
"common.save": "Salva",
|
||||
"common.cancel": "Annulla",
|
||||
"common.settings": "Impostazioni",
|
||||
@@ -85,6 +86,11 @@
|
||||
"sidebar.noPostsYet": "Nessun post",
|
||||
"sidebar.noPagesYet": "Nessuna pagina",
|
||||
"sidebar.noMediaYet": "Nessun media",
|
||||
"sidebar.noScriptsYet": "Nessuno script",
|
||||
"sidebar.noTemplatesYet": "Nessun modello",
|
||||
"sidebar.chatPlaceholder": "L'assistente IA apparirà qui",
|
||||
"sidebar.importPlaceholder": "Importa contenuti da fonti esterne",
|
||||
"sidebar.gitPlaceholder": "Integrazione del controllo sorgente",
|
||||
"sidebar.loading": "Caricamento...",
|
||||
"sidebar.settingsHeader": "Impostazioni",
|
||||
"sidebar.tagsHeader": "Tag",
|
||||
|
||||