// Button component - Pure Windjammer implementation
// Cross-platform: renders to HTML (web) or egui (desktop)
// NO `mut` keyword needed - compiler infers everything!
use super::traits::Renderable
use super::traits::RenderableVNode
use super::vnode::VNode
@auto
pub enum ButtonVariant {
Primary,
Secondary,
Success,
Danger,
Warning,
Ghost,
}
@auto
pub enum ButtonSize {
Small,
Medium,
Large,
}
@auto
pub struct Button {
label: string,
variant: ButtonVariant,
size: ButtonSize,
disabled: bool,
}
impl Button {
// Constructor - returns new Button
pub fn new(label: string) -> Button {
Button {
label: label,
variant: ButtonVariant::Primary,
size: ButtonSize::Medium,
disabled: false,
}
}
// Builder pattern: consumes self, modifies, returns self
// Compiler infers: mut self (owned, mutable)
pub fn variant(self, variant: ButtonVariant) -> Button {
self.variant = variant
self
}
// Builder pattern: consumes self, modifies, returns self
// Compiler infers: mut self (owned, mutable)
pub fn size(self, size: ButtonSize) -> Button {
self.size = size
self
}
// Builder pattern: consumes self, modifies, returns self
// Compiler infers: mut self (owned, mutable)
pub fn disabled(self, disabled: bool) -> Button {
self.disabled = disabled
self
}
// Get variant CSS class
fn get_variant_class(&self) -> string {
match self.variant {
ButtonVariant::Primary => "wj-button-primary".to_string(),
ButtonVariant::Secondary => "wj-button-secondary".to_string(),
ButtonVariant::Success => "wj-button-success".to_string(),
ButtonVariant::Danger => "wj-button-danger".to_string(),
ButtonVariant::Warning => "wj-button-warning".to_string(),
ButtonVariant::Ghost => "wj-button-ghost".to_string(),
}
}
// Get size CSS class
fn get_size_class(&self) -> string {
match self.size {
ButtonSize::Small => "wj-button-sm".to_string(),
ButtonSize::Medium => "wj-button-md".to_string(),
ButtonSize::Large => "wj-button-lg".to_string(),
}
}
// Get inline style based on variant and size
fn get_style(&self) -> string {
let base = "border: none; border-radius: 4px; cursor: pointer; font-weight: 500; transition: all 0.2s;".to_string()
let size_style = match self.size {
ButtonSize::Small => " padding: 4px 8px; font-size: 12px;",
ButtonSize::Medium => " padding: 8px 16px; font-size: 14px;",
ButtonSize::Large => " padding: 12px 24px; font-size: 16px;",
}
let variant_style = match self.variant {
ButtonVariant::Primary => " background: #4A9EFF; color: white;",
ButtonVariant::Secondary => " background: #333; color: #e0e0e0; border: 1px solid #555;",
ButtonVariant::Success => " background: #44AA44; color: white;",
ButtonVariant::Danger => " background: #FF4444; color: white;",
ButtonVariant::Warning => " background: #FFAA44; color: white;",
ButtonVariant::Ghost => " background: transparent; color: #4A9EFF;",
}
let disabled_style = if self.disabled {
" opacity: 0.5; cursor: not-allowed;"
} else {
""
}
format!("{}{}{}{}", base, size_style, variant_style, disabled_style)
}
}
// Implement RenderableVNode for cross-platform rendering
impl RenderableVNode for Button {
fn to_vnode(&self) -> VNode {
VNode::button()
.add_class("wj-button".to_string())
.add_class(self.get_variant_class())
.add_class(self.get_size_class())
.add_style(self.get_style())
.set_disabled(self.disabled)
.add_text(self.label.clone())
}
}
// Implement Renderable for backward compatibility (HTML string output)
impl Renderable for Button {
fn render(self) -> string {
// Build variant class
let variant_class = match self.variant {
ButtonVariant::Primary => "wj-button-primary",
ButtonVariant::Secondary => "wj-button-secondary",
ButtonVariant::Success => "wj-button-success",
ButtonVariant::Danger => "wj-button-danger",
ButtonVariant::Warning => "wj-button-warning",
ButtonVariant::Ghost => "wj-button-ghost",
}
// Build size class
let size_class = match self.size {
ButtonSize::Small => "wj-button-sm",
ButtonSize::Medium => "wj-button-md",
ButtonSize::Large => "wj-button-lg",
}
// Build disabled attr
let disabled_attr = if self.disabled { " disabled='true'" } else { "" }
format!("<button class='wj-button {} {}' style='{}'{}>{}</button>",
variant_class, size_class, self.get_style(), disabled_attr, self.label)
}
}
// Test the Button component
fn main() {
let button1 = Button::new("Click Me".to_string())
.variant(ButtonVariant::Primary)
.size(ButtonSize::Large)
let button2 = Button::new("Cancel".to_string())
.variant(ButtonVariant::Danger)
.disabled(true)
// Test both rendering methods
println!("Button 1 (HTML): {}", button1.clone().render())
println!("Button 2 (HTML): {}", button2.clone().render())
// Test VNode rendering
let vnode1 = button1.to_vnode()
let vnode2 = button2.to_vnode()
println!("Button 1 VNode handle: {}", vnode1.raw_handle())
println!("Button 2 VNode handle: {}", vnode2.raw_handle())
}