use std::{fmt::Debug, rc::Rc};
use nami::{Binding, Computed, SignalExt as _, impl_constant, signal::IntoComputed};
use waterui_core::handler::{AnyViewBuilder, ViewBuilder};
use waterui_core::{AnyView, Dynamic, Environment, IgnorableMetadata, View};
use waterui_graphics::Color;
use waterui_layout::{Point, Rect, Size};
use waterui_str::Str;
use crate::app::application_name;
#[cfg(feature = "snackbar")]
use crate::snackbar::SnackbarManager;
use crate::{
ViewExt,
background::{Material, MaterialBackground},
component::label::LabelDisplayMode,
prelude::FullScreenOverlayManager,
};
#[derive(Debug)]
pub struct Window {
pub title: Computed<Str>,
pub closable: bool,
pub resizable: bool,
pub frame: Binding<Rect>,
pub content: AnyViewBuilder<AnyView>,
pub state: Binding<WindowState>,
pub toolbar: Option<AnyView>,
pub style: WindowStyle,
pub background: WindowBackground,
pub min_size: Option<Computed<Size>>,
pub max_size: Option<Computed<Size>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WindowState {
Normal,
#[default]
Closed,
Minimized,
Fullscreen,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WindowStyle {
#[default]
Titled,
Borderless,
FullSizeContentView,
}
#[derive(Debug, Clone, Default)]
pub enum WindowBackground {
#[default]
Opaque,
Color(Color),
}
#[derive(Debug)]
pub enum WindowBackgroundInput {
Color(Color),
Material(Material),
}
impl From<Color> for WindowBackgroundInput {
fn from(color: Color) -> Self {
Self::Color(color)
}
}
impl From<Material> for WindowBackgroundInput {
fn from(material: Material) -> Self {
Self::Material(material)
}
}
#[derive(Clone)]
pub struct WindowManager(Rc<dyn Fn(Window)>);
impl Debug for WindowManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowManager").finish()
}
}
impl WindowManager {
pub fn new<F: 'static + Fn(Window)>(show: F) -> Self {
Self(Rc::new(show))
}
pub fn show(&self, window: Window) {
(self.0)(window);
}
}
impl_constant!(WindowState);
impl_constant!(WindowStyle);
impl Window {
#[must_use]
pub fn new(
title: impl IntoComputed<Str>,
state: Binding<WindowState>,
content: impl ViewBuilder,
) -> Self {
let default_frame = Rect::new(Point::zero(), Size::new(800.0, 600.0));
let (overlay_manager, overlay_view) = FullScreenOverlayManager::new();
#[cfg(feature = "snackbar")]
let (snackbar_manager, snackbar_view) = SnackbarManager::new();
let content = AnyViewBuilder::new(move || {
let overlay_manager = overlay_manager.clone();
#[cfg(feature = "snackbar")]
let snackbar_manager = snackbar_manager.clone();
let content = content
.build()
.overlay(overlay_view.clone())
.with(overlay_manager.clone())
.state(&overlay_manager);
#[cfg(feature = "snackbar")]
let content = content
.overlay(snackbar_view.clone())
.with(snackbar_manager.clone())
.state(&snackbar_manager);
AnyView::new(content)
});
Self {
title: title.into_computed(),
closable: true,
resizable: true,
frame: Binding::container(default_frame),
content,
state,
toolbar: None,
style: WindowStyle::default(),
background: WindowBackground::default(),
min_size: None,
max_size: None,
}
}
#[must_use]
pub fn min_size(mut self, size: impl IntoComputed<Size>) -> Self {
self.min_size = Some(size.into_computed());
self
}
#[must_use]
pub fn max_size(mut self, size: impl IntoComputed<Size>) -> Self {
self.max_size = Some(size.into_computed());
self
}
#[must_use]
pub const fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
#[must_use]
pub fn toolbar(mut self, toolbar: impl View) -> Self {
self.toolbar = Some(AnyView::new(toolbar.install(LabelDisplayMode::IconOnly)));
self
}
#[must_use]
pub const fn style(mut self, style: WindowStyle) -> Self {
self.style = style;
self
}
#[must_use]
pub fn background(mut self, background: impl Into<WindowBackgroundInput>) -> Self {
match background.into() {
WindowBackgroundInput::Color(color) => {
self.background = WindowBackground::Color(color);
}
WindowBackgroundInput::Material(material) => {
self.background = WindowBackground::Opaque;
let content = self.content;
self.content = AnyViewBuilder::new(move || {
AnyView::new(IgnorableMetadata::new(
content.build(),
MaterialBackground(material),
))
});
}
}
self
}
pub fn build_content(&self) -> AnyView {
self.content.build()
}
#[must_use]
pub fn title(mut self, title: impl IntoComputed<Str>) -> Self {
self.title = title.into_computed();
self
}
#[must_use]
pub fn display_title(&self) -> Computed<Str> {
let application = application_name();
self.title
.map(move |declared| {
if declared.is_empty() {
application.clone()
} else {
declared
}
})
.into_computed()
}
#[must_use]
pub fn handle(&self) -> WindowHandle {
WindowHandle {
frame: self.frame.clone(),
state: self.state.clone(),
}
}
pub fn show(self, env: &Environment) {
env.get::<WindowManager>()
.expect("WindowManager not found in environment")
.show(self);
}
}
impl View for Window {
fn body(self, env: &Environment) -> impl View {
self.show(env);
}
}
#[derive(Debug, Clone)]
pub struct WindowPresentation {
state: Binding<WindowState>,
presented: Binding<bool>,
}
impl WindowPresentation {
#[must_use]
pub fn new(state: &Binding<WindowState>) -> Self {
Self {
state: state.clone(),
presented: Binding::container(false),
}
}
#[must_use]
pub fn state(&self) -> Binding<WindowState> {
self.state.clone()
}
}
pub fn conditional_window<F>(presentation: &WindowPresentation, creator: F) -> impl View + use<F>
where
F: Fn(Binding<WindowState>) -> Window + 'static,
{
let state = presentation.state.clone();
let presented = presentation.presented.clone();
Dynamic::watch(state.clone(), move |s| {
if s == WindowState::Closed {
presented.set(false);
AnyView::new(())
} else if !presented.get() {
presented.set(true);
AnyView::new(creator(state.clone()))
} else {
AnyView::new(())
}
})
}
#[derive(Debug, Clone)]
pub struct WindowHandle {
frame: Binding<Rect>,
state: Binding<WindowState>,
}
impl WindowHandle {
pub fn close(&self) {
self.state.set(WindowState::Closed);
}
pub fn minimize(&self) {
self.state.set(WindowState::Minimized);
}
pub fn fullscreen(&self) {
self.state.set(WindowState::Fullscreen);
}
pub fn restore(&self) {
self.state.set(WindowState::Normal);
}
pub fn set_frame(&self, frame: Rect) {
self.frame.set(frame);
}
}