zsui 0.3.0-alpha.2

Rust-first feature-gated native UI framework for Win32, AppKit, and Linux.
Documentation
use crate::{Point, UiRect};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SettingsComponentKind {
    Label,
    TextInput,
    Toggle,
    Dropdown,
    Button,
    AccentButton,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NativeControlFamily {
    StaticText,
    TextInput,
    Action,
}

impl SettingsComponentKind {
    pub const fn family(self) -> NativeControlFamily {
        match self {
            SettingsComponentKind::Label => NativeControlFamily::StaticText,
            SettingsComponentKind::TextInput => NativeControlFamily::TextInput,
            SettingsComponentKind::Toggle
            | SettingsComponentKind::Dropdown
            | SettingsComponentKind::Button
            | SettingsComponentKind::AccentButton => NativeControlFamily::Action,
        }
    }

    pub const fn is_action(self) -> bool {
        matches!(self.family(), NativeControlFamily::Action)
    }
}

pub trait NativeControlMapper {
    type ClassName: Copy + Eq;

    fn class_name(&self, kind: SettingsComponentKind) -> Self::ClassName;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NativeControlMapperOperation {
    ClassName,
}

impl NativeControlMapperOperation {
    pub const fn operation_name(self) -> &'static str {
        match self {
            Self::ClassName => "class_name",
        }
    }
}

pub const REQUIRED_NATIVE_CONTROL_MAPPER_OPERATIONS: [NativeControlMapperOperation; 1] =
    [NativeControlMapperOperation::ClassName];

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SettingsControlSpec {
    pub id: Option<i64>,
    pub text: String,
    pub kind: SettingsComponentKind,
    pub bounds: UiRect,
}

impl SettingsControlSpec {
    pub fn new(
        kind: SettingsComponentKind,
        id: Option<i64>,
        text: impl Into<String>,
        bounds: UiRect,
    ) -> Self {
        Self {
            id,
            text: text.into(),
            kind,
            bounds,
        }
    }

    pub fn action(
        kind: SettingsComponentKind,
        id: i64,
        text: impl Into<String>,
        bounds: UiRect,
    ) -> Self {
        debug_assert!(kind.is_action());
        Self::new(kind, Some(id), text, bounds)
    }

    pub fn label(text: impl Into<String>, bounds: UiRect) -> Self {
        Self::new(SettingsComponentKind::Label, None, text, bounds)
    }

    pub fn text_input(id: i64, text: impl Into<String>, bounds: UiRect) -> Self {
        Self::new(SettingsComponentKind::TextInput, Some(id), text, bounds)
    }

    pub const fn width(&self) -> i32 {
        self.bounds.right - self.bounds.left
    }

    pub const fn height(&self) -> i32 {
        self.bounds.bottom - self.bounds.top
    }
}

pub trait NativeSettingsControlHost {
    type Handle: Copy + Eq;

    fn create_control(&mut self, spec: &SettingsControlSpec) -> Self::Handle;
    fn destroy_control(&mut self, handle: Self::Handle);
    fn control_exists(&self, handle: Self::Handle) -> bool;
    fn set_control_visible(&mut self, handle: Self::Handle, visible: bool);
    fn set_control_enabled(&mut self, handle: Self::Handle, enabled: bool);
    fn set_control_bounds(&mut self, handle: Self::Handle, bounds: UiRect);
    fn control_at_point(&self, point: Point) -> Option<Self::Handle>;
    fn control_screen_bounds(&self, handle: Self::Handle) -> Option<UiRect>;
    fn control_text(&self, handle: Self::Handle) -> String;
    fn set_control_text(&mut self, handle: Self::Handle, text: &str);
    fn request_control_repaint(&mut self, handle: Self::Handle) -> bool;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingsControlHostOperation {
    CreateControl,
    DestroyControl,
    ControlExists,
    SetControlVisible,
    SetControlEnabled,
    SetControlBounds,
    ControlAtPoint,
    ControlScreenBounds,
    ControlText,
    SetControlText,
    RequestControlRepaint,
}

impl SettingsControlHostOperation {
    pub const fn operation_name(self) -> &'static str {
        match self {
            Self::CreateControl => "create_control",
            Self::DestroyControl => "destroy_control",
            Self::ControlExists => "control_exists",
            Self::SetControlVisible => "set_control_visible",
            Self::SetControlEnabled => "set_control_enabled",
            Self::SetControlBounds => "set_control_bounds",
            Self::ControlAtPoint => "control_at_point",
            Self::ControlScreenBounds => "control_screen_bounds",
            Self::ControlText => "control_text",
            Self::SetControlText => "set_control_text",
            Self::RequestControlRepaint => "request_control_repaint",
        }
    }
}

pub const REQUIRED_SETTINGS_CONTROL_HOST_OPERATIONS: [SettingsControlHostOperation; 11] = [
    SettingsControlHostOperation::CreateControl,
    SettingsControlHostOperation::DestroyControl,
    SettingsControlHostOperation::ControlExists,
    SettingsControlHostOperation::SetControlVisible,
    SettingsControlHostOperation::SetControlEnabled,
    SettingsControlHostOperation::SetControlBounds,
    SettingsControlHostOperation::ControlAtPoint,
    SettingsControlHostOperation::ControlScreenBounds,
    SettingsControlHostOperation::ControlText,
    SettingsControlHostOperation::SetControlText,
    SettingsControlHostOperation::RequestControlRepaint,
];

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn settings_control_spec_keeps_geometry_and_intent() {
        let spec = SettingsControlSpec::action(
            SettingsComponentKind::AccentButton,
            7,
            "Save",
            UiRect::new(10, 20, 90, 52),
        );

        assert_eq!(spec.kind.family(), NativeControlFamily::Action);
        assert_eq!(spec.width(), 80);
        assert_eq!(spec.height(), 32);
        assert_eq!(
            REQUIRED_SETTINGS_CONTROL_HOST_OPERATIONS[0].operation_name(),
            "create_control"
        );
    }
}