pub use super::render::register_input_coordinator_sidebar;
use super::render::register_context_manager_sidebar;
use super::settings::SidebarSettings;
use super::state::{SidebarState, MAX_SIDEBAR_WIDTH, MIN_SIDEBAR_WIDTH};
use super::types::{SidebarRenderKind, SidebarView};
use crate::layout::docking::DockPanel;
use crate::input::core::coordinator::LayerId;
use crate::input::{Sense, WidgetKind};
use crate::layout::{ChevronStepDirection, CompositeKind, CompositeRegistration, DispatchEvent, LayoutManager, LayoutNodeId, SidebarHandle, SidebarNode, WidgetNode};
use crate::render::RenderContext;
use crate::types::{Rect, WidgetId};
use crate::ui::widgets::atomic::text::render::draw_text;
use crate::ui::widgets::atomic::text::settings::TextSettings;
use crate::ui::widgets::atomic::text::types::{TextOverflow, TextView};
use crate::render::{TextAlign, TextBaseline};
pub struct ConsumeEventCtx {
pub cursor: (f64, f64),
pub frame_rect: Rect,
pub viewport: (f64, f64),
}
pub fn consume_event(
event: DispatchEvent,
state: &mut SidebarState,
host_id: &WidgetId,
ctx: ConsumeEventCtx,
) -> Option<DispatchEvent> {
match event {
DispatchEvent::ChevronStepRequested { ref chevron_id, direction } => {
let is_own = chevron_id.0 == format!("{}:chevron_up", host_id.0)
|| chevron_id.0 == format!("{}:chevron_down", host_id.0);
if is_own {
let step = 40.0_f64;
let signed = match direction {
ChevronStepDirection::Up | ChevronStepDirection::Left => -step,
_ => step,
};
let scroll = state.get_or_insert_scroll("default");
scroll.offset = (scroll.offset + signed).max(0.0);
None
} else {
Some(event)
}
}
DispatchEvent::ResizeHandleDragStarted { host_id: ref hid, edge } => {
if hid == host_id {
let min_size = MIN_SIDEBAR_WIDTH;
let cap_size = (ctx.viewport.0.max(ctx.viewport.1)).max(MAX_SIDEBAR_WIDTH);
state.start_resize(edge, ctx.frame_rect, ctx.cursor, min_size, cap_size);
None
} else {
Some(event)
}
}
DispatchEvent::ScrollbarTrackClicked { ref track_id } => {
if track_id.0 == format!("{}:scrollbar_track", host_id.0) {
Some(event)
} else {
Some(event)
}
}
DispatchEvent::ScrollbarThumbDragStarted { ref thumb_id } => {
if thumb_id.0 == format!("{}:scrollbar_handle", host_id.0) {
state.get_or_insert_scroll("default").start_drag(ctx.cursor.1);
None
} else {
Some(event)
}
}
_ => Some(event),
}
}
pub fn drag_outcome_sidebar(
state: &SidebarState,
which: &'static str,
sidebar_rect: crate::types::Rect,
est_content_h: f64,
) -> Option<crate::layout::DragOutcome> {
if state.resize_drag.is_some() {
return Some(crate::layout::DragOutcome::SidebarResize { which });
}
if let Some(scroll) = state.scroll_per_panel.get("default") {
if scroll.is_dragging {
let track_rect = SidebarState::scrollbar_track_rect(sidebar_rect);
let viewport_h = track_rect.height;
return Some(crate::layout::DragOutcome::SidebarScrollbar {
track_rect,
content_h: est_content_h,
viewport_h,
});
}
}
None
}
pub fn register_layout_manager_sidebar<P: DockPanel>(
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
parent: LayoutNodeId,
slot_id: &str,
handle: &SidebarHandle,
view: &mut SidebarView<'_>,
settings: &SidebarSettings,
kind: &SidebarRenderKind,
) -> Option<SidebarNode> {
let id: WidgetId = handle.id.clone();
let rect = layout.rect_for_edge_slot(slot_id)?;
let mut state = layout.sidebars_map_mut().remove(&id).unwrap_or_default();
let layer = layout.compute_layer_for(parent);
if let Some(win) = layout.last_window() {
let is_horizontal_kind = !matches!(kind, super::types::SidebarRenderKind::Top | super::types::SidebarRenderKind::Bottom);
state.ensure_sized(win.width, win.height, is_horizontal_kind);
}
let node_id = layout.tree_mut().add_widget(parent, WidgetNode { id: id.clone(), kind: WidgetKind::Sidebar, rect, sense: Sense::CLICK, label: None });
{
use crate::layout::{EventBuilder, ResizeEdge};
let edge = match kind {
super::types::SidebarRenderKind::Left
| super::types::SidebarRenderKind::WithTypeSelector => ResizeEdge::E,
super::types::SidebarRenderKind::Right => ResizeEdge::W,
super::types::SidebarRenderKind::Top => ResizeEdge::S,
super::types::SidebarRenderKind::Bottom => ResizeEdge::N,
super::types::SidebarRenderKind::Embedded => ResizeEdge::E,
super::types::SidebarRenderKind::Custom(_) => ResizeEdge::E,
};
layout.dispatcher_mut().on_exact(
format!("{}:resize", id.0),
EventBuilder::ResizeHandle { host_id: id.clone(), edge },
);
}
{
use crate::layout::{ChevronStepDirection, EventBuilder};
layout.dispatcher_mut().on_exact(
format!("{}:scrollbar_track", id.0),
EventBuilder::ScrollbarTrack { track_id: WidgetId(format!("{}:scrollbar_track", id.0)) },
);
layout.dispatcher_mut().on_exact(
format!("{}:scrollbar_handle", id.0),
EventBuilder::ScrollbarThumb { thumb_id: WidgetId(format!("{}:scrollbar_handle", id.0)) },
);
for (suffix, dir) in [
("chevron_up", ChevronStepDirection::Up),
("chevron_down", ChevronStepDirection::Down),
("chevron_left", ChevronStepDirection::Left),
("chevron_right", ChevronStepDirection::Right),
] {
let cid = WidgetId(format!("{}:{}", id.0, suffix));
layout.dispatcher_mut().on_exact(
format!("{}:{}", id.0, suffix),
EventBuilder::ChevronStep { chevron_id: cid, direction: dir },
);
}
}
register_context_manager_sidebar(
layout.ctx_mut(), render, id.clone(), rect, &mut state, view, settings, kind, &layer,
);
layout.push_composite_registration(CompositeRegistration {
kind: CompositeKind::Sidebar,
slot_id: slot_id.to_string(),
widget_id: id.clone(),
frame_rect: rect,
});
layout.sidebars_map_mut().insert(id, state);
Some(SidebarNode(node_id))
}
pub fn handle_sidebar_resize(state: &mut SidebarState, new_width: f64) {
state.width = new_width.clamp(MIN_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH);
}
pub fn handle_sidebar_resize_clamped(state: &mut SidebarState, new_size: f64, min: f64, max: f64) {
state.width = new_size.clamp(min, max);
}
pub fn handle_sidebar_scroll(
state: &mut SidebarState,
panel_id: &str,
delta: f64,
content_height: f64,
viewport_height: f64,
) {
let scroll = state.get_or_insert_scroll(panel_id);
let max_scroll = (content_height - viewport_height).max(0.0);
scroll.offset = (scroll.offset + delta).clamp(0.0, max_scroll);
}
pub fn handle_sidebar_collapse_toggle(state: &mut SidebarState) {
state.toggle_collapse();
}
pub struct SidebarRadioItem<'a> {
pub id: &'a str,
pub label: &'a str,
pub selected: bool,
}
pub struct SidebarPanelEntry<'a> {
pub close_id: &'a str,
pub title: &'a str,
pub active: bool,
}
pub struct SidebarBodyBuilder<'a, P: DockPanel> {
render: &'a mut dyn RenderContext,
layout: &'a mut LayoutManager<P>,
layer: LayerId,
bx: f64,
bw: f64,
y: f64,
}
impl<'a, P: DockPanel> SidebarBodyBuilder<'a, P> {
pub fn new(
render: &'a mut dyn RenderContext,
layout: &'a mut LayoutManager<P>,
body_rect: Rect,
content_origin_y: f64,
layer: LayerId,
) -> Self {
let bx = body_rect.x + 8.0;
let bw = body_rect.width - 16.0;
Self { render, layout, layer, bx, bw, y: content_origin_y + 8.0 }
}
pub fn add_section_header(&mut self, text: &str) {
draw_text(
self.render,
Rect::new(self.bx, self.y, self.bw, 22.0),
&TextView { text, align: TextAlign::Left, baseline: TextBaseline::Middle,
color: Some("rgba(255,255,255,0.4)"), font: None, overflow: TextOverflow::Clip, hovered: false },
&TextSettings::default(),
);
self.y += 22.0;
}
pub fn add_sub_label(&mut self, text: &str) {
draw_text(
self.render,
Rect::new(self.bx, self.y, self.bw, 20.0),
&TextView { text, align: TextAlign::Left, baseline: TextBaseline::Middle,
color: Some("rgba(255,255,255,0.55)"), font: None, overflow: TextOverflow::Clip, hovered: false },
&TextSettings::default(),
);
self.y += 20.0;
}
pub fn add_spacer(&mut self, height: f64) {
self.y += height;
}
pub fn add_divider(&mut self) {
self.render.set_fill_color("rgba(255,255,255,0.08)");
self.render.fill_rect(self.bx, self.y, self.bw, 1.0);
self.y += 10.0;
}
pub fn add_radio_group(&mut self, items: &[SidebarRadioItem<'_>]) {
let bx = self.bx;
let bw = self.bw;
for item in items {
let rx = bx + 6.0;
let ry = self.y;
if item.selected {
self.render.set_fill_color("#2962ff");
} else {
self.render.set_fill_color("rgba(255,255,255,0.18)");
}
self.render.fill_rounded_rect(rx, ry + 3.0, 10.0, 10.0, 5.0);
draw_text(
self.render,
Rect::new(rx + 16.0, ry, bw - 22.0, 20.0),
&TextView {
text: item.label,
align: TextAlign::Left,
baseline: TextBaseline::Middle,
color: Some(if item.selected { "#ffffff" } else { "#a0a0b0" }),
font: None, overflow: TextOverflow::Clip, hovered: false,
},
&TextSettings::default(),
);
let layer = self.layer.clone();
self.layout.ctx_mut().input.register_atomic(
WidgetId(item.id.to_owned()),
WidgetKind::Button,
Rect::new(bx, ry, bw, 20.0),
Sense::CLICK | Sense::HOVER,
&layer,
);
self.y += 22.0;
}
}
pub fn add_action_button(&mut self, id: &str, label: &str) {
let bx = self.bx;
let bw = self.bw;
let y = self.y;
self.render.set_fill_color("#2962ff");
self.render.fill_rounded_rect(bx, y, bw, 28.0, 4.0);
draw_text(
self.render,
Rect::new(bx, y, bw, 28.0),
&TextView { text: label, align: TextAlign::Center, baseline: TextBaseline::Middle,
color: Some("#ffffff"), font: None, overflow: TextOverflow::Clip, hovered: false },
&TextSettings::default(),
);
let layer = self.layer.clone();
self.layout.ctx_mut().input.register_atomic(
WidgetId(id.to_owned()),
WidgetKind::Button,
Rect::new(bx, y, bw, 28.0),
Sense::CLICK | Sense::HOVER,
&layer,
);
self.y += 36.0;
}
pub fn add_panel_list(
&mut self,
entries: &[SidebarPanelEntry<'_>],
close_label: &str,
) {
let bx = self.bx;
let bw = self.bw;
for entry in entries {
let y = self.y;
self.render.set_fill_color(if entry.active {
"rgba(41,98,255,0.18)"
} else {
"rgba(255,255,255,0.05)"
});
self.render.fill_rounded_rect(bx, y, bw, 26.0, 3.0);
draw_text(
self.render,
Rect::new(bx + 10.0, y, bw - 36.0, 26.0),
&TextView {
text: entry.title,
align: TextAlign::Left,
baseline: TextBaseline::Middle,
color: Some(if entry.active { "#4d90fe" } else { "#d1d4dc" }),
font: None, overflow: TextOverflow::Clip, hovered: false,
},
&TextSettings::default(),
);
let close_x = bx + bw - 22.0;
draw_text(
self.render,
Rect::new(close_x, y + 5.0, 16.0, 16.0),
&TextView { text: close_label, align: TextAlign::Center,
baseline: TextBaseline::Middle, color: Some("rgba(255,80,80,0.5)"),
font: None, overflow: TextOverflow::Clip, hovered: false },
&TextSettings::default(),
);
let layer = self.layer.clone();
self.layout.ctx_mut().input.register_atomic(
WidgetId(entry.close_id.to_owned()),
WidgetKind::Button,
Rect::new(close_x, y + 5.0, 16.0, 16.0),
Sense::CLICK | Sense::HOVER,
&layer,
);
self.y += 30.0;
}
}
pub fn finish(self) {
self.render.restore();
}
pub fn current_y(&self) -> f64 {
self.y
}
}