#[cfg(not(target_arch = "wasm32"))]
use crate::app::runner::AppRunner;
use crate::clipboard::{ClipboardConfig, ClipboardError, ClipboardProvider, ClipboardReporter};
#[cfg(not(target_arch = "wasm32"))]
use crate::core::component::Component;
use crate::overlay::ToastPlacement;
use crate::style::Padding;
use crate::style::{Color, Paint, Style, Theme};
use std::path::PathBuf;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub(crate) enum ViewportMode {
#[default]
Fullscreen,
Inline { height: u16 },
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum InlineStartupPolicy {
#[default]
PreserveHost,
ClearHost,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SurfaceMode {
#[default]
Fullscreen,
InlineEphemeral {
height: u16,
},
InlineTranscript {
height: u16,
startup: InlineStartupPolicy,
},
}
impl SurfaceMode {
pub(crate) fn is_inline(&self) -> bool {
!matches!(self, Self::Fullscreen)
}
pub(crate) fn normalized(self) -> Self {
match self {
Self::Fullscreen => Self::Fullscreen,
Self::InlineEphemeral { height } => Self::InlineEphemeral {
height: height.max(1),
},
Self::InlineTranscript { height, startup } => Self::InlineTranscript {
height: height.max(1),
startup,
},
}
}
pub(crate) fn viewport_mode(self) -> ViewportMode {
match self.normalized() {
Self::Fullscreen => ViewportMode::Fullscreen,
Self::InlineEphemeral { height } | Self::InlineTranscript { height, .. } => {
ViewportMode::Inline { height }
}
}
}
pub(crate) fn clear_on_start(self) -> bool {
matches!(
self,
Self::InlineTranscript {
startup: InlineStartupPolicy::ClearHost,
..
}
)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum TextAreaNewlineBinding {
#[default]
Enter,
ShiftEnter,
EnterOrShiftEnter,
}
#[cfg_attr(
feature = "terminal-serde",
derive(serde::Serialize, serde::Deserialize)
)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum ContrastPolicy {
Off,
#[default]
Wcag,
BlackOrWhite,
Apca,
}
#[cfg(all(test, feature = "terminal-serde"))]
mod terminal_serde_tests {
use super::*;
#[test]
fn contrast_policy_round_trips() {
let policy = ContrastPolicy::BlackOrWhite;
let json = serde_json::to_string(&policy).unwrap();
assert_eq!(
serde_json::from_str::<ContrastPolicy>(&json).unwrap(),
policy
);
}
}
#[cfg(feature = "devtools")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct DevToolsConfig {
pub logs: bool,
pub metrics: bool,
pub show_framework_logs: bool,
}
#[cfg(feature = "devtools")]
impl Default for DevToolsConfig {
fn default() -> Self {
Self {
logs: true,
metrics: true,
show_framework_logs: true,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum ScreenBackground {
#[default]
Transparent,
Theme,
Custom(Style),
}
impl ScreenBackground {
pub(crate) fn resolve(self, theme: &Theme) -> Option<Style> {
match self {
Self::Transparent => None,
Self::Theme => Some(Style::new().bg(theme.surface.backdrop)),
Self::Custom(style) => (!style.is_empty()).then_some(style),
}
}
}
impl From<Style> for ScreenBackground {
fn from(style: Style) -> Self {
Self::Custom(style)
}
}
impl From<Color> for ScreenBackground {
fn from(color: Color) -> Self {
Self::Custom(Style::new().bg(color))
}
}
impl From<Paint> for ScreenBackground {
fn from(paint: Paint) -> Self {
Self::Custom(Style::new().bg(paint))
}
}
pub struct App {
pub(crate) title: Option<String>,
pub(crate) surface_mode: SurfaceMode,
pub(crate) mouse_enabled: Option<bool>,
pub(crate) scroll_wheel_multiplier: u16,
pub(crate) theme: Theme,
pub(crate) toast_placement: ToastPlacement,
pub(crate) toast_gap: u16,
pub(crate) toast_margin: Padding,
pub(crate) clipboard_config: ClipboardConfig,
pub(crate) keymap_path: Option<PathBuf>,
pub(crate) text_area_newline_binding: TextAreaNewlineBinding,
pub(crate) contrast_policy: ContrastPolicy,
pub(crate) clipboard_provider: Option<Box<dyn ClipboardProvider>>,
pub(crate) clipboard_reporter: ClipboardReporter,
pub(crate) terminal_bg: Option<Color>,
pub(crate) live_host_terminal_colors: bool,
pub(crate) system_theme: bool,
pub(crate) screen_background: ScreenBackground,
#[cfg(feature = "devtools")]
pub(crate) devtools_config: DevToolsConfig,
}
impl Default for App {
fn default() -> Self {
let theme = Theme::default();
Self {
title: None,
surface_mode: SurfaceMode::default(),
mouse_enabled: None,
scroll_wheel_multiplier: 1,
theme,
toast_placement: ToastPlacement::default(),
toast_gap: 1,
toast_margin: Padding::BORDER,
clipboard_config: ClipboardConfig::default(),
keymap_path: None,
text_area_newline_binding: TextAreaNewlineBinding::default(),
contrast_policy: ContrastPolicy::default(),
clipboard_provider: None,
clipboard_reporter: crate::clipboard::default_clipboard_reporter(),
terminal_bg: None,
live_host_terminal_colors: false,
system_theme: false,
screen_background: ScreenBackground::default(),
#[cfg(feature = "devtools")]
devtools_config: DevToolsConfig::default(),
}
}
}
impl App {
pub fn new() -> Self {
Self::default()
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn surface(mut self, mode: SurfaceMode) -> Self {
self.surface_mode = mode.normalized();
self
}
pub fn fullscreen(self) -> Self {
self.surface(SurfaceMode::Fullscreen)
}
pub fn inline_ephemeral(self, height: u16) -> Self {
self.surface(SurfaceMode::InlineEphemeral { height })
}
pub fn inline_transcript(self, height: u16) -> Self {
self.surface(SurfaceMode::InlineTranscript {
height,
startup: InlineStartupPolicy::PreserveHost,
})
}
pub fn inline_transcript_with_startup(self, height: u16, startup: InlineStartupPolicy) -> Self {
self.surface(SurfaceMode::InlineTranscript { height, startup })
}
pub fn mouse(mut self, enabled: bool) -> Self {
self.mouse_enabled = Some(enabled);
self
}
pub fn scroll_wheel_multiplier(mut self, multiplier: u16) -> Self {
self.scroll_wheel_multiplier = multiplier.max(1);
self
}
pub fn theme(mut self, theme: Theme) -> Self {
self.theme = theme;
self
}
pub fn screen_background(mut self, background: impl Into<ScreenBackground>) -> Self {
self.screen_background = background.into();
self
}
pub fn fill_background(mut self) -> Self {
self.screen_background = ScreenBackground::Theme;
self
}
pub fn toast_placement(mut self, placement: ToastPlacement) -> Self {
self.toast_placement = placement;
self
}
pub fn toast_gap(mut self, gap: u16) -> Self {
self.toast_gap = gap;
self
}
pub fn toast_margin(mut self, margin: impl Into<Padding>) -> Self {
self.toast_margin = margin.into();
self
}
pub fn clipboard_config(mut self, config: ClipboardConfig) -> Self {
self.clipboard_config = config;
self
}
pub fn keymap_path(mut self, path: impl Into<PathBuf>) -> Self {
self.keymap_path = Some(path.into());
self
}
pub fn text_area_newline_binding(mut self, binding: TextAreaNewlineBinding) -> Self {
self.text_area_newline_binding = binding;
self
}
pub fn contrast_policy(mut self, policy: ContrastPolicy) -> Self {
self.contrast_policy = policy;
self
}
pub fn clipboard_provider(mut self, provider: impl ClipboardProvider + 'static) -> Self {
self.clipboard_provider = Some(Box::new(provider));
self
}
pub fn clipboard_reporter(mut self, reporter: impl Fn(ClipboardError) + 'static) -> Self {
self.clipboard_reporter = std::rc::Rc::new(reporter);
self
}
pub fn terminal_bg(mut self, color: Option<Color>) -> Self {
self.terminal_bg = color;
self
}
pub fn live_host_terminal_colors(mut self, enabled: bool) -> Self {
self.live_host_terminal_colors = enabled;
self
}
pub fn system_theme(mut self) -> Self {
self.system_theme = true;
self
}
#[cfg(feature = "devtools")]
pub fn devtools_config(mut self, config: DevToolsConfig) -> Self {
self.devtools_config = config;
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn mount<C>(self, component: C) -> AppRunner<C>
where
C: Component,
C::Properties: Default,
{
self.mount_with_props(component, C::Properties::default())
}
#[cfg(not(target_arch = "wasm32"))]
pub fn mount_with_props<C>(self, component: C, props: C::Properties) -> AppRunner<C>
where
C: Component,
{
AppRunner::new(self, component, props)
}
}