use crate::core::types::Rect;
use crate::layout::docking::DockPanel;
use crate::layout::{LayoutManager, LayoutNodeId, SidebarHandle, SidebarNode, StyleManager};
use crate::render::RenderContext;
use crate::types::OverflowMode;
use crate::ui::widgets::composite::sidebar::input::register_layout_manager_sidebar;
use crate::ui::widgets::composite::sidebar::settings::SidebarSettings;
use crate::ui::widgets::composite::sidebar::style::{DefaultSidebarStyle, SidebarStyle};
use crate::ui::widgets::composite::sidebar::theme::{DefaultSidebarTheme, SidebarTheme};
use crate::ui::widgets::composite::sidebar::types::{
HeaderAction, SidebarHeader, SidebarHeaderMode, SidebarRenderKind, SidebarTab, SidebarView,
};
struct StyledSidebarTheme {
bg: String,
border: String,
header_text: String,
tab_accent: String,
tab_bg_active: String,
fallback: DefaultSidebarTheme,
}
impl StyledSidebarTheme {
fn from_styles(s: &StyleManager) -> Self {
let accent = s.color_or_owned("accent", "#2962ff");
let accent_dim = s.color_or_owned("accent_dim","rgba(41,98,255,0.12)");
Self {
bg: s.color_or_owned("surface", "#1e222d"),
border: s.color_or_owned("border_strong","#363a45"),
header_text: s.color_or_owned("fg_0", "#ffffff"),
tab_accent: accent,
tab_bg_active: accent_dim,
fallback: DefaultSidebarTheme,
}
}
}
impl SidebarTheme for StyledSidebarTheme {
fn bg(&self) -> &str { &self.bg }
fn border(&self) -> &str { &self.border }
fn header_bg(&self) -> &str { &self.bg }
fn header_text(&self) -> &str { &self.header_text }
fn header_icon(&self) -> &str { self.fallback.header_icon() }
fn divider(&self) -> &str { &self.border }
fn action_icon_normal(&self) -> &str { self.fallback.action_icon_normal() }
fn action_icon_hover(&self) -> &str { self.fallback.action_icon_hover() }
fn scrollbar_thumb(&self) -> &str { self.fallback.scrollbar_thumb() }
fn scrollbar_thumb_active(&self) -> &str { self.fallback.scrollbar_thumb_active() }
fn tab_text_active(&self) -> &str { &self.header_text }
fn tab_text_inactive(&self) -> &str { self.fallback.tab_text_inactive() }
fn tab_accent(&self) -> &str { &self.tab_accent }
fn tab_bg_active(&self) -> &str { &self.tab_bg_active }
fn tab_bg_hover(&self) -> &str { self.fallback.tab_bg_hover() }
}
fn sidebar_settings_from_styles(s: &StyleManager) -> SidebarSettings {
SidebarSettings {
theme: Box::new(StyledSidebarTheme::from_styles(s)),
style: Box::<DefaultSidebarStyle>::default(),
}
}
pub struct SidebarBuilder<'a> {
handle: &'a SidebarHandle,
slot_id: &'a str,
parent: LayoutNodeId,
header_icon: Option<&'a crate::types::IconId>,
header_title: &'a str,
header_actions: &'a [HeaderAction<'a>],
header_mode: SidebarHeaderMode,
tabs: &'a [SidebarTab<'a>],
active_tab: Option<&'a str>,
show_scrollbar: bool,
content_height: f64,
overflow: OverflowMode,
settings: Option<SidebarSettings>,
theme_override: Option<Box<dyn SidebarTheme>>,
style_override: Option<Box<dyn SidebarStyle>>,
kind: SidebarRenderKind,
}
pub fn sidebar<'a>(handle: &'a SidebarHandle, slot_id: &'a str) -> SidebarBuilder<'a> {
SidebarBuilder::new(handle, slot_id)
}
impl<'a> SidebarBuilder<'a> {
pub fn new(handle: &'a SidebarHandle, slot_id: &'a str) -> Self {
Self {
handle,
slot_id,
parent: LayoutNodeId::ROOT,
header_icon: None,
header_title: "",
header_actions: &[],
header_mode: SidebarHeaderMode::default(),
tabs: &[],
active_tab: None,
show_scrollbar: false,
content_height: 0.0,
overflow: OverflowMode::Clip,
settings: None,
theme_override: None,
style_override: None,
kind: SidebarRenderKind::Left,
}
}
pub fn parent(mut self, p: LayoutNodeId) -> Self { self.parent = p; self }
pub fn header_icon(mut self, icon: &'a crate::types::IconId) -> Self {
self.header_icon = Some(icon); self
}
pub fn header_title(mut self, t: &'a str) -> Self { self.header_title = t; self }
pub fn header_actions(mut self, a: &'a [HeaderAction<'a>]) -> Self {
self.header_actions = a; self
}
pub fn header_mode(mut self, m: SidebarHeaderMode) -> Self { self.header_mode = m; self }
pub fn tabs(mut self, ts: &'a [SidebarTab<'a>]) -> Self { self.tabs = ts; self }
pub fn active_tab(mut self, id: &'a str) -> Self { self.active_tab = Some(id); self }
pub fn show_scrollbar(mut self, on: bool) -> Self { self.show_scrollbar = on; self }
pub fn content_height(mut self, h: f64) -> Self { self.content_height = h; self }
pub fn overflow(mut self, m: OverflowMode) -> Self { self.overflow = m; self }
pub fn settings(mut self, s: SidebarSettings) -> Self { self.settings = Some(s); self }
pub fn kind(mut self, k: SidebarRenderKind) -> Self { self.kind = k; self }
pub fn theme(mut self, t: Box<dyn SidebarTheme>) -> Self {
self.theme_override = Some(t);
self
}
pub fn style(mut self, s: Box<dyn SidebarStyle>) -> Self {
self.style_override = Some(s);
self
}
pub fn build<P: DockPanel>(
self,
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
) -> Option<SidebarNode> {
self.build_with_body(layout, render, |_, _, _: Rect| {})
}
pub fn build_with_body<P, F>(
self,
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
body: F,
) -> Option<SidebarNode>
where
P: DockPanel,
F: FnOnce(&mut LayoutManager<P>, &mut dyn RenderContext, Rect),
{
let mut view = SidebarView {
header: SidebarHeader {
icon: self.header_icon,
title: self.header_title,
actions: self.header_actions,
},
header_mode: self.header_mode,
tabs: self.tabs,
active_tab: self.active_tab,
show_scrollbar: self.show_scrollbar,
content_height: self.content_height,
overflow: self.overflow,
};
let mut settings = self.settings.unwrap_or_else(|| sidebar_settings_from_styles(layout.styles()));
if let Some(t) = self.theme_override { settings.theme = t; }
if let Some(s) = self.style_override { settings.style = s; }
let panel_key = self.active_tab.unwrap_or("default").to_string();
let scroll_off = layout
.sidebars_map_mut()
.get(&self.handle.id)
.and_then(|s| s.scroll_per_panel.get(panel_key.as_str()))
.map(|s| s.offset)
.unwrap_or(0.0);
let node = register_layout_manager_sidebar(
layout,
render,
self.parent,
self.slot_id,
self.handle,
&mut view,
&settings,
&self.kind,
);
let frame = layout
.rect_for_edge_slot(self.slot_id)
.unwrap_or(Rect::new(0.0, 0.0, 0.0, 0.0));
if frame.width > 0.0 && frame.height > 0.0 {
let body_rect = if let Some(state) = layout.sidebars_map_mut().get(&self.handle.id) {
let vp = crate::ui::widgets::composite::sidebar::render::body_viewport(
frame, state, &view, &settings, &self.kind,
);
vp.clip_rect
} else {
frame
};
render.save();
render.clip_rect(body_rect.x, body_rect.y, body_rect.width, body_rect.height);
match self.overflow {
OverflowMode::Scrollbar => {
render.translate(0.0, -scroll_off);
}
OverflowMode::Compress => {
if self.content_height > body_rect.height && self.content_height > 0.0 {
let s = body_rect.height / self.content_height;
render.translate(body_rect.x, body_rect.y);
render.scale(s, s);
render.translate(-body_rect.x, -body_rect.y);
}
}
_ => {}
}
body(layout, render, body_rect);
render.restore();
}
node
}
}