use crate::core::types::Rect;
use crate::docking::panels::DockPanel;
use crate::layout::{ContextMenuHandle, ContextMenuNode, LayoutManager, LayoutNodeId, StyleManager};
use crate::render::RenderContext;
use crate::ui::widgets::composite::context_menu::input::register_layout_manager_context_menu;
use crate::ui::widgets::composite::context_menu::settings::ContextMenuSettings;
use crate::ui::widgets::composite::context_menu::style::DefaultContextMenuStyle;
use crate::ui::widgets::composite::context_menu::theme::{ContextMenuTheme, DefaultContextMenuTheme};
use crate::ui::widgets::composite::context_menu::types::{
ContextMenuItem, ContextMenuRenderKind, ContextMenuView,
};
struct StyledContextMenuTheme {
bg: String,
border: String,
item_bg_hover: String,
item_text: String,
fallback: DefaultContextMenuTheme,
}
impl StyledContextMenuTheme {
fn from_styles(s: &StyleManager) -> Self {
Self {
bg: s.color_or_owned("surface", "#1e222d"),
border: s.color_or_owned("border_strong", "#363a45"),
item_bg_hover: s.color_or_owned("surface_raised","#2a2e39"),
item_text: s.color_or_owned("fg_1", "#d1d4dc"),
fallback: DefaultContextMenuTheme,
}
}
}
impl ContextMenuTheme for StyledContextMenuTheme {
fn bg(&self) -> &str { &self.bg }
fn border(&self) -> &str { &self.border }
fn shadow(&self) -> &str { self.fallback.shadow() }
fn item_bg_normal(&self) -> &str { &self.bg }
fn item_bg_hover(&self) -> &str { &self.item_bg_hover }
fn item_bg_danger_hover(&self) -> &str { self.fallback.item_bg_danger_hover() }
fn item_text(&self) -> &str { &self.item_text }
fn item_text_hover(&self) -> &str { self.fallback.item_text_hover() }
fn item_text_disabled(&self) -> &str { self.fallback.item_text_disabled() }
fn item_text_danger(&self) -> &str { self.fallback.item_text_danger() }
fn separator(&self) -> &str { &self.border }
}
fn context_menu_settings_from_styles(s: &StyleManager) -> ContextMenuSettings {
ContextMenuSettings {
theme: Box::new(StyledContextMenuTheme::from_styles(s)),
style: Box::<DefaultContextMenuStyle>::default(),
}
}
pub struct ContextMenuBuilder<'a> {
handle: &'a ContextMenuHandle,
parent: LayoutNodeId,
slot_id: Option<&'a str>,
overlay_rect: Option<Rect>,
anchor: Option<Rect>,
anchor_widget_id: Option<&'a str>,
origin: (f64, f64),
items: &'a [ContextMenuItem<'a>],
target_id: Option<&'a str>,
title: Option<&'a str>,
settings: Option<ContextMenuSettings>,
kind: ContextMenuRenderKind<'a>,
}
pub fn context_menu<'a>(handle: &'a ContextMenuHandle) -> ContextMenuBuilder<'a> {
ContextMenuBuilder::new(handle)
}
impl<'a> ContextMenuBuilder<'a> {
pub fn new(handle: &'a ContextMenuHandle) -> Self {
Self {
handle,
parent: LayoutNodeId::ROOT,
slot_id: None,
overlay_rect: None,
anchor: None,
anchor_widget_id: None,
origin: (0.0, 0.0),
items: &[],
target_id: None,
title: None,
settings: None,
kind: ContextMenuRenderKind::Default,
}
}
pub fn parent(mut self, p: LayoutNodeId) -> Self { self.parent = p; self }
pub fn slot_id(mut self, s: &'a str) -> Self { self.slot_id = Some(s); self }
pub fn origin(mut self, o: (f64, f64)) -> Self { self.origin = o; self }
pub fn rect(mut self, r: Rect) -> Self { self.overlay_rect = Some(r); self }
pub fn anchor(mut self, r: Rect) -> Self { self.anchor = Some(r); self }
pub fn anchor_to(mut self, widget_id: &'a str) -> Self {
self.anchor_widget_id = Some(widget_id);
self
}
pub fn items(mut self, items: &'a [ContextMenuItem<'a>]) -> Self { self.items = items; self }
pub fn target_id(mut self, id: &'a str) -> Self { self.target_id = Some(id); self }
pub fn title(mut self, t: &'a str) -> Self { self.title = Some(t); self }
pub fn settings(mut self, s: ContextMenuSettings) -> Self { self.settings = Some(s); self }
pub fn kind(mut self, k: ContextMenuRenderKind<'a>) -> Self { self.kind = k; self }
pub fn build<P: DockPanel>(
self,
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
) -> Option<ContextMenuNode> {
let slot_id = self.slot_id
.map(str::to_owned)
.unwrap_or_else(|| self.handle.id_str().to_string());
let resolved_anchor: Option<Rect> = self.anchor.or_else(|| {
self.anchor_widget_id.and_then(|wid| {
layout.ctx().input.widget_rect(&crate::types::unsafe_widget_id(wid))
})
});
let overlay_rect = self.overlay_rect.unwrap_or_else(|| {
let (x, y) = if self.origin == (0.0, 0.0) {
resolved_anchor
.map(|a| (a.x, a.y + a.height))
.unwrap_or(self.origin)
} else {
self.origin
};
Rect::new(x, y, 180.0, 240.0)
});
let mut view = ContextMenuView {
items: self.items,
target_id: self.target_id,
title: self.title,
};
let settings = self.settings.unwrap_or_else(|| context_menu_settings_from_styles(layout.styles()));
register_layout_manager_context_menu(
layout,
render,
self.parent,
&slot_id,
self.handle,
overlay_rect,
resolved_anchor,
&mut view,
&settings,
&self.kind,
)
}
}