use crate::core::types::Rect;
use crate::docking::panels::DockPanel;
use crate::layout::{LayoutManager, LayoutNodeId, ModalHandle, ModalNode, StyleManager};
use crate::render::RenderContext;
use crate::ui::widgets::composite::modal::input::register_layout_manager_modal;
use crate::ui::widgets::composite::modal::settings::ModalSettings;
use crate::ui::widgets::composite::modal::style::DefaultModalStyle;
use crate::ui::widgets::composite::modal::theme::{DefaultModalTheme, ModalTheme};
use crate::ui::widgets::composite::modal::types::{
BackdropKind, FooterBtn, ModalRenderKind, ModalView, WizardPageInfo,
};
use crate::types::OverflowMode;
struct StyledModalTheme {
bg: String,
border: String,
header_text: String,
tab_accent: String,
tab_bg_active: String,
fallback: DefaultModalTheme,
}
impl StyledModalTheme {
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.clone(),
tab_bg_active: accent_dim,
fallback: DefaultModalTheme,
}
}
}
impl ModalTheme for StyledModalTheme {
fn bg(&self) -> &str { &self.bg }
fn border(&self) -> &str { &self.border }
fn shadow(&self) -> &str { self.fallback.shadow() }
fn header_bg(&self) -> &str { &self.bg }
fn header_text(&self) -> &str { &self.header_text }
fn divider(&self) -> &str { &self.border }
fn footer_bg(&self) -> &str { &self.bg }
fn footer_border(&self) -> &str { &self.border }
fn close_icon(&self) -> &str { self.fallback.close_icon() }
fn close_icon_hover(&self) -> &str { self.fallback.close_icon_hover() }
fn backdrop_dim(&self) -> &str { self.fallback.backdrop_dim() }
fn backdrop_full(&self) -> &str { self.fallback.backdrop_full() }
fn sidebar_bg(&self) -> &str { &self.bg }
fn sidebar_border(&self) -> &str { &self.border }
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 wizard_dot_inactive(&self) -> &str { self.fallback.wizard_dot_inactive() }
fn wizard_dot_active(&self) -> &str { &self.tab_accent }
}
fn modal_settings_from_styles(s: &StyleManager) -> ModalSettings {
ModalSettings {
theme: Box::new(StyledModalTheme::from_styles(s)),
style: Box::<DefaultModalStyle>::default(),
}
}
pub struct ModalBuilder<'a> {
handle: &'a ModalHandle,
parent: LayoutNodeId,
slot_id: Option<&'a str>,
overlay_rect: Option<Rect>,
anchor: Option<Rect>,
title: Option<&'a str>,
tabs: &'a [&'a str],
footer: &'a [FooterBtn<'a>],
wizard_pages: &'a [WizardPageInfo<'a>],
backdrop: BackdropKind,
overflow: OverflowMode,
resizable: bool,
settings: Option<ModalSettings>,
kind: ModalRenderKind,
}
pub fn modal<'a>(handle: &'a ModalHandle) -> ModalBuilder<'a> {
ModalBuilder::new(handle)
}
impl<'a> ModalBuilder<'a> {
pub fn new(handle: &'a ModalHandle) -> Self {
Self {
handle,
parent: LayoutNodeId::ROOT,
slot_id: None,
overlay_rect: None,
anchor: None,
title: None,
tabs: &[],
footer: &[],
wizard_pages: &[],
backdrop: BackdropKind::Dim,
overflow: OverflowMode::Clip,
resizable: false,
settings: None,
kind: ModalRenderKind::WithHeader,
}
}
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 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 title(mut self, t: &'a str) -> Self { self.title = Some(t); self }
pub fn tabs(mut self, ts: &'a [&'a str]) -> Self { self.tabs = ts; self }
pub fn footer(mut self, btns: &'a [FooterBtn<'a>]) -> Self { self.footer = btns; self }
pub fn wizard_pages(mut self, pages: &'a [WizardPageInfo<'a>]) -> Self {
self.wizard_pages = pages;
self
}
pub fn backdrop(mut self, b: BackdropKind) -> Self { self.backdrop = b; self }
pub fn overflow(mut self, m: OverflowMode) -> Self { self.overflow = m; self }
pub fn resizable(mut self, on: bool) -> Self { self.resizable = on; self }
pub fn settings(mut self, s: ModalSettings) -> Self { self.settings = Some(s); self }
pub fn kind(mut self, k: ModalRenderKind) -> Self { self.kind = k; self }
pub fn build<P: DockPanel>(
self,
layout: &mut LayoutManager<P>,
render: &mut dyn RenderContext,
) -> Option<ModalNode> {
let slot_id = self.slot_id
.map(str::to_owned)
.unwrap_or_else(|| self.handle.id_str().to_string());
let overlay_rect = self.overlay_rect.unwrap_or_else(|| default_modal_rect(layout));
let mut view = ModalView {
title: self.title,
tabs: self.tabs,
footer_buttons: self.footer,
wizard_pages: self.wizard_pages,
backdrop: self.backdrop,
overflow: self.overflow,
resizable: self.resizable,
};
let settings = self.settings.unwrap_or_else(|| modal_settings_from_styles(layout.styles()));
register_layout_manager_modal(
layout,
render,
self.parent,
&slot_id,
self.handle,
overlay_rect,
self.anchor,
&mut view,
&settings,
&self.kind,
)
}
}
fn default_modal_rect<P: DockPanel>(layout: &LayoutManager<P>) -> Rect {
let (w, h) = (600.0_f64, 400.0_f64);
let viewport = layout.last_window().unwrap_or(Rect::new(0.0, 0.0, 1280.0, 800.0));
Rect::new(
viewport.x + (viewport.width - w) / 2.0,
viewport.y + (viewport.height - h) / 2.0,
w, h,
)
}