Skip to content

界面组件类型 (Widget)

界面组件类型是 Valkyrie 中专门为 UI 开发设计的特殊类类型。它提供了构建现代用户界面的高级抽象,支持响应式设计、状态管理和事件处理。

基本组件定义

简单组件

valkyrie
# 基本按钮组件
widget Button {
    # 组件属性
    text: String,
    enabled: bool,
    style: ButtonStyle,
    
    # 事件处理器
    on_click: Option<micro() -> ()>,
    
    # 构造函数
    new(text: String) {
        self.text = text
        self.enabled = true
        self.style = ButtonStyle::default()
        self.on_click = None
    }
    
    # 渲染方法
    render(self) -> Element {
        Element::button()
            .text(self.text)
            .enabled(self.enabled)
            .style(self.style)
            .on_click(self.on_click)
    }
    
    # 设置点击事件
    on_click(mut self, handler: micro() -> ()) -> Self {
        self.on_click = Some(handler)
        self
    }
    
    # 设置样式
    with_style(mut self, style: ButtonStyle) -> Self {
        self.style = style
        self
    }
}

文本输入组件

valkyrie
widget TextInput {
    value: String,
    placeholder: String,
    max_length: Option<usize>,
    readonly: bool,
    
    # 事件处理器
    on_change: Option<micro(String) -> ()>,
    on_focus: Option<micro() -> ()>,
    on_blur: Option<micro() -> ()>,
    
    new(placeholder: String = "") {
        self.value = ""
        self.placeholder = placeholder
        self.max_length = None
        self.readonly = false
        self.on_change = None
        self.on_focus = None
        self.on_blur = None
    }
    
    render(self) -> Element {
        Element::input()
            .value(self.value)
            .placeholder(self.placeholder)
            .max_length(self.max_length)
            .readonly(self.readonly)
            .on_change(self.on_change)
            .on_focus(self.on_focus)
            .on_blur(self.on_blur)
    }
    
    # 设置值
    set_value(mut self, value: String) {
        if let Some(max_len) = self.max_length {
            if value.len() > max_len {
                return
            }
        }
        
        self.value = value
        
        if let Some(handler) = self.on_change {
            handler(self.value.clone())
        }
    }
    
    # 链式配置
    max_length(mut self, length: usize) -> Self {
        self.max_length = Some(length)
        self
    }
    
    readonly(mut self, readonly: bool = true) -> Self {
        self.readonly = readonly
        self
    }
}

布局组件

容器组件

valkyrie
widget Container {
    children: [Box<dyn Widget>],
    padding: Padding,
    margin: Margin,
    background: Option<Color>,
    border: Option<Border>,
    
    new() {
        self.children = []
        self.padding = Padding::zero()
        self.margin = Margin::zero()
        self.background = None
        self.border = None
    }
    
    render(self) -> Element {
        let mut element = Element::div()
            .padding(self.padding)
            .margin(self.margin)
        
        if let Some(bg) = self.background {
            element = element.background(bg)
        }
        
        if let Some(border) = self.border {
            element = element.border(border)
        }
        
        for child in self.children {
            element = element.child(child.render())
        }
        
        element
    }
    
    # 添加子组件
    add_child(mut self, child: Box<dyn Widget>) -> Self {
        self.children.push(child)
        self
    }
    
    # 批量添加子组件
    add_children(mut self, children: [Box<dyn Widget>]) -> Self {
        self.children.extend(children)
        self
    }
    
    # 设置样式
    padding(mut self, padding: Padding) -> Self {
        self.padding = padding
        self
    }
    
    background(mut self, color: Color) -> Self {
        self.background = Some(color)
        self
    }
}

弹性布局

valkyrie
widget FlexBox {
    children: [FlexChild],
    direction: FlexDirection,
    justify_content: JustifyContent,
    align_items: AlignItems,
    wrap: FlexWrap,
    gap: f32,
    
    new(direction: FlexDirection = FlexDirection::Row) {
        self.children = []
        self.direction = direction
        self.justify_content = JustifyContent::Start
        self.align_items = AlignItems::Stretch
        self.wrap = FlexWrap::NoWrap
        self.gap = 0.0
    }
    
    render(self) -> Element {
        let mut element = Element::div()
            .display(Display::Flex)
            .flex_direction(self.direction)
            .justify_content(self.justify_content)
            .align_items(self.align_items)
            .flex_wrap(self.wrap)
            .gap(self.gap)
        
        for child in self.children {
            let child_element = child.widget.render()
                .flex_grow(child.grow)
                .flex_shrink(child.shrink)
                .flex_basis(child.basis)
            
            element = element.child(child_element)
        }
        
        element
    }
    
    # 添加弹性子项
    add_flex_child(mut self, widget: Box<dyn Widget>, grow: f32 = 0.0, shrink: f32 = 1.0, basis: FlexBasis = FlexBasis::Auto) -> Self {
        self.children.push(FlexChild {
            widget,
            grow,
            shrink,
            basis,
        })
        self
    }
    
    # 设置布局属性
    justify_content(mut self, justify: JustifyContent) -> Self {
        self.justify_content = justify
        self
    }
    
    align_items(mut self, align: AlignItems) -> Self {
        self.align_items = align
        self
    }
}

网格布局

valkyrie
widget Grid {
    children: [GridChild],
    template_columns: [GridTrack],
    template_rows: [GridTrack],
    gap: GridGap,
    
    new(columns: [GridTrack], rows: [GridTrack]) {
        self.children = []
        self.template_columns = columns
        self.template_rows = rows
        self.gap = GridGap::zero()
    }
    
    render(self) -> Element {
        let mut element = Element::div()
            .display(Display::Grid)
            .grid_template_columns(self.template_columns)
            .grid_template_rows(self.template_rows)
            .gap(self.gap)
        
        for child in self.children {
            let child_element = child.widget.render()
                .grid_column(child.column)
                .grid_row(child.row)
            
            element = element.child(child_element)
        }
        
        element
    }
    
    # 添加网格子项
    add_grid_child(mut self, widget: Box<dyn Widget>, column: GridPosition, row: GridPosition) -> Self {
        self.children.push(GridChild {
            widget,
            column,
            row,
        })
        self
    }
}

状态管理组件

有状态组件

valkyrie
widget Counter {
    count: i32,
    step: i32,
    min_value: Option<i32>,
    max_value: Option<i32>,
    
    # 事件处理器
    on_change: Option<micro(i32) -> ()>,
    
    new(initial_count: i32 = 0, step: i32 = 1) {
        self.count = initial_count
        self.step = step
        self.min_value = None
        self.max_value = None
        self.on_change = None
    }
    
    render(self) -> Element {
        FlexBox::new(FlexDirection::Row)
            .add_flex_child(
                Box::new(Button::new("-")
                    .on_click({ || self.decrement() })),
                0.0, 1.0, FlexBasis::Auto
            )
            .add_flex_child(
                Box::new(Text::new(self.count)
                    .align(TextAlign::Center)),
                1.0, 1.0, FlexBasis::Auto
            )
            .add_flex_child(
                Box::new(Button::new("+")
                    .on_click({ || self.increment() })),
                0.0, 1.0, FlexBasis::Auto
            )
            .render()
    }
    
    # 增加计数
    increment(mut self) {
        let new_count = self.count + self.step
        
        if let Some(max) = self.max_value {
            if new_count > max {
                return
            }
        }
        
        self.count = new_count
        self.notify_change()
    }
    
    # 减少计数
    decrement(mut self) {
        let new_count = self.count - self.step
        
        if let Some(min) = self.min_value {
            if new_count < min {
                return
            }
        }
        
        self.count = new_count
        self.notify_change()
    }
    
    # 通知变化
    notify_change(self) {
        if let Some(handler) = self.on_change {
            handler(self.count)
        }
    }
    
    # 设置范围
    range(mut self, min: i32, max: i32) -> Self {
        self.min_value = Some(min)
        self.max_value = Some(max)
        self
    }
}

表单组件

valkyrie
widget Form<T> {
    fields: {String: Box<dyn Widget>},
    validators: {String: [Validator]},
    data: T,
    errors: {String: [String]},
    
    # 事件处理器
    on_submit: Option<micro(T) -> ()>,
    on_validate: Option<micro(ValidationResult) -> ()>,
    
    new(initial_data: T) {
        self.fields = {}
        self.validators = {}
        self.data = initial_data
        self.errors = {}
        self.on_submit = None
        self.on_validate = None
    }
    
    render(self) -> Element {
        let mut form_element = Element::form()
            .on_submit({ |e| 
                e.prevent_default()
                self.handle_submit()
            })
        
        # 渲染字段
        for (name, field) in self.fields {
            let field_container = Container::new()
                .add_child(field)
            
            # 添加错误信息
            if let Some(field_errors) = self.errors.get(name) {
                for error in field_errors {
                    field_container = field_container.add_child(
                        Box::new(Text::new(error)
                            .color(Color::Red)
                            .size(TextSize::Small))
                    )
                }
            }
            
            form_element = form_element.child(field_container.render())
        }
        
        # 提交按钮
        form_element = form_element.child(
            Button::new("提交")
                .type_(ButtonType::Submit)
                .render()
        )
        
        form_element
    }
    
    # 添加字段
    add_field(mut self, name: String, field: Box<dyn Widget>) -> Self {
        self.fields.insert(name, field)
        self
    }
    
    # 添加验证器
    add_validator(mut self, field_name: String, validator: Validator) -> Self {
        if !self.validators.contains_key(field_name) {
            self.validators.insert(field_name.clone(), [])
        }
        self.validators[field_name].push(validator)
        self
    }
    
    # 验证表单
    validate(mut self) -> ValidationResult {
        self.errors.clear()
        let mut is_valid = true
        
        for (field_name, validators) in self.validators {
            let field_value = self.get_field_value(field_name)
            
            for validator in validators {
                if let Err(error) = validator.validate(field_value) {
                    if !self.errors.contains_key(field_name) {
                        self.errors.insert(field_name.clone(), [])
                    }
                    self.errors[field_name].push(error)
                    is_valid = false
                }
            }
        }
        
        let result = new ValidationResult { is_valid, errors: self.errors.clone() }
        
        if let Some(handler) = self.on_validate {
            handler(result.clone())
        }
        
        result
    }
    
    # 处理提交
    handle_submit(mut self) {
        let validation_result = self.validate()
        
        if validation_result.is_valid {
            if let Some(handler) = self.on_submit {
                handler(self.data.clone())
            }
        }
    }
}

高级组件

虚拟滚动列表

valkyrie
widget VirtualList<T> {
    items: [T],
    item_height: f32,
    container_height: f32,
    scroll_top: f32,
    
    # 渲染函数
    render_item: micro(T, usize) -> Box<dyn Widget>,
    
    new(items: [T], item_height: f32, container_height: f32) {
        self.items = items
        self.item_height = item_height
        self.container_height = container_height
        self.scroll_top = 0.0
        self.render_item = { |item, index| 
            Box::new(Text::new(@format("Item {}", index)))
        }
    }
    
    render(self) -> Element {
        let visible_start = (self.scroll_top / self.item_height).floor() as usize
        let visible_count = (self.container_height / self.item_height).ceil() as usize + 1
        let visible_end = (visible_start + visible_count).min(self.items.len())
        
        let mut container = Container::new()
            .height(self.container_height)
            .overflow_y(Overflow::Scroll)
            .on_scroll({ |e| self.handle_scroll(e.scroll_top) })
        
        # 上方占位符
        if visible_start > 0 {
            let spacer_height = visible_start as f32 * self.item_height
            container = container.add_child(
                Box::new(Spacer::new().height(spacer_height))
            )
        }
        
        # 可见项目
        for i in visible_start..visible_end {
            let item = &self.items[i]
            let item_widget = (self.render_item)(item.clone(), i)
            container = container.add_child(item_widget)
        }
        
        # 下方占位符
        if visible_end < self.items.len() {
            let spacer_height = (self.items.len() - visible_end) as f32 * self.item_height
            container = container.add_child(
                Box::new(Spacer::new().height(spacer_height))
            )
        }
        
        container.render()
    }
    
    # 处理滚动
    handle_scroll(mut self, scroll_top: f32) {
        self.scroll_top = scroll_top
        # 触发重新渲染
        self.request_update()
    }
    
    # 设置项目渲染器
    item_renderer(mut self, renderer: micro(T, usize) -> Box<dyn Widget>) -> Self {
        self.render_item = renderer
        self
    }
}

模态对话框

valkyrie
widget Modal {
    visible: bool,
    title: String,
    content: Box<dyn Widget>,
    closable: bool,
    
    # 事件处理器
    on_close: Option<micro() -> ()>,
    on_confirm: Option<micro() -> ()>,
    
    new(title: String, content: Box<dyn Widget>) {
        self.visible = false
        self.title = title
        self.content = content
        self.closable = true
        self.on_close = None
        self.on_confirm = None
    }
    
    render(self) -> Element {
        if !self.visible {
            return Element::empty()
        }
        
        # 遮罩层
        let overlay = Element::div()
            .position(Position::Fixed)
            .top(0)
            .left(0)
            .width("100%")
            .height("100%")
            .background(Color::rgba(0, 0, 0, 0.5))
            .z_index(1000)
            .on_click({ || 
                if self.closable {
                    self.close()
                }
            })
        
        # 对话框内容
        let dialog = Container::new()
            .background(Color::White)
            .border_radius(8.0)
            .padding(Padding::all(20.0))
            .max_width(500.0)
            .position(Position::Relative)
            .on_click({ |e| e.stop_propagation() })
        
        # 标题栏
        let header = FlexBox::new(FlexDirection::Row)
            .justify_content(JustifyContent::SpaceBetween)
            .align_items(AlignItems::Center)
            .add_flex_child(
                Box::new(Text::new(self.title)
                    .size(TextSize::Large)
                    .weight(FontWeight::Bold)),
                1.0, 1.0, FlexBasis::Auto
            )
        
        if self.closable {
            header = header.add_flex_child(
                Box::new(Button::new("×")
                    .variant(ButtonVariant::Ghost)
                    .on_click({ || self.close() })),
                0.0, 1.0, FlexBasis::Auto
            )
        }
        
        dialog = dialog
            .add_child(Box::new(header))
            .add_child(self.content.clone())
        
        # 居中显示
        let centered = FlexBox::new(FlexDirection::Column)
            .justify_content(JustifyContent::Center)
            .align_items(AlignItems::Center)
            .width("100%")
            .height("100%")
            .add_flex_child(Box::new(dialog), 0.0, 1.0, FlexBasis::Auto)
        
        overlay.child(centered.render())
    }
    
    # 显示对话框
    show(mut self) {
        self.visible = true
        self.request_update()
    }
    
    # 关闭对话框
    close(mut self) {
        self.visible = false
        
        if let Some(handler) = self.on_close {
            handler()
        }
        
        self.request_update()
    }
}

响应式设计

媒体查询组件

valkyrie
widget Responsive {
    breakpoints: {String: f32},
    current_breakpoint: String,
    children: {String: Box<dyn Widget>},
    
    new() {
        self.breakpoints = {
            "mobile": 768.0,
            "tablet": 1024.0,
            "desktop": 1200.0,
        }
        self.current_breakpoint = "desktop"
        self.children = {}
        
        # 监听窗口大小变化
        self.setup_resize_listener()
    }
    
    render(self) -> Element {
        if let Some(child) = self.children.get(self.current_breakpoint) {
            child.render()
        } else {
            Element::empty()
        }
    }
    
    # 为不同断点设置组件
    for_breakpoint(mut self, breakpoint: String, widget: Box<dyn Widget>) -> Self {
        self.children.insert(breakpoint, widget)
        self
    }
    
    # 设置断点
    breakpoint(mut self, name: String, width: f32) -> Self {
        self.breakpoints.insert(name, width)
        self
    }
    
    # 更新当前断点
    update_breakpoint(mut self, window_width: f32) {
        let mut current = "mobile"
        
        for (name, width) in self.breakpoints {
            if window_width >= width {
                current = name
            }
        }
        
        if current != self.current_breakpoint {
            self.current_breakpoint = current
            self.request_update()
        }
    }
}

动画和过渡

动画组件

valkyrie
widget Animated {
    child: Box<dyn Widget>,
    animation: Animation,
    duration: Duration,
    easing: EasingFunction,
    
    new(child: Box<dyn Widget>, animation: Animation) {
        self.child = child
        self.animation = animation
        self.duration = Duration::milliseconds(300)
        self.easing = EasingFunction::EaseInOut
    }
    
    render(self) -> Element {
        self.child.render()
            .animate(self.animation)
            .duration(self.duration)
            .easing(self.easing)
    }
    
    # 设置动画属性
    duration(mut self, duration: Duration) -> Self {
        self.duration = duration
        self
    }
    
    easing(mut self, easing: EasingFunction) -> Self {
        self.easing = easing
        self
    }
}

# 预定义动画
union Animation {
    FadeIn,
    FadeOut,
    SlideInLeft,
    SlideInRight,
    SlideInUp,
    SlideInDown,
    ScaleIn,
    ScaleOut,
    RotateIn,
    Bounce,
}

最佳实践

1. 组件组合

valkyrie
# 复合组件示例
widget UserCard {
    user: User,
    show_actions: bool,
    
    new(user: User, show_actions: bool = true) {
        self.user = user
        self.show_actions = show_actions
    }
    
    render(self) -> Element {
        let mut card = Container::new()
            .padding(Padding::all(16.0))
            .border(Border::all(1.0, Color::Gray))
            .border_radius(8.0)
            .background(Color::White)
        
        # 用户头像和信息
        let user_info = FlexBox::new(FlexDirection::Row)
            .gap(12.0)
            .add_flex_child(
                Box::new(Avatar::new(self.user.avatar_url)
                    .size(48.0)),
                0.0, 1.0, FlexBasis::Auto
            )
            .add_flex_child(
                Box::new(FlexBox::new(FlexDirection::Column)
                    .add_flex_child(
                        Box::new(Text::new(self.user.name)
                            .size(TextSize::Large)
                            .weight(FontWeight::Bold)),
                        0.0, 1.0, FlexBasis::Auto
                    )
                    .add_flex_child(
                        Box::new(Text::new(self.user.email)
                            .color(Color::Gray)),
                        0.0, 1.0, FlexBasis::Auto
                    )),
                1.0, 1.0, FlexBasis::Auto
            )
        
        card = card.add_child(Box::new(user_info))
        
        # 操作按钮
        if self.show_actions {
            let actions = FlexBox::new(FlexDirection::Row)
                .gap(8.0)
                .justify_content(JustifyContent::End)
                .add_flex_child(
                    Box::new(Button::new("编辑")
                        .variant(ButtonVariant::Outline)),
                    0.0, 1.0, FlexBasis::Auto
                )
                .add_flex_child(
                    Box::new(Button::new("删除")
                        .variant(ButtonVariant::Danger)),
                    0.0, 1.0, FlexBasis::Auto
                )
            
            card = card.add_child(Box::new(actions))
        }
        
        card.render()
    }
}

2. 状态管理

valkyrie
# 使用状态管理的组件
widget TodoApp {
    todos: [Todo],
    filter: TodoFilter,
    new_todo_text: String,
    
    new() {
        self.todos = []
        self.filter = TodoFilter::All
        self.new_todo_text = ""
    }
    
    render(self) -> Element {
        Container::new()
            .padding(Padding::all(20.0))
            .add_child(Box::new(self.render_header()))
            .add_child(Box::new(self.render_todo_list()))
            .add_child(Box::new(self.render_footer()))
            .render()
    }
    
    render_header(self) -> imply Widget {
        FlexBox::new(FlexDirection::Row)
            .gap(10.0)
            .add_flex_child(
                Box::new(TextInput::new("添加新任务...")
                    .value(self.new_todo_text)
                    .on_change({ |text| self.new_todo_text = text })
                    .on_enter({ || self.add_todo() })),
                1.0, 1.0, FlexBasis::Auto
            )
            .add_flex_child(
                Box::new(Button::new("添加")
                    .on_click({ || self.add_todo() })),
                0.0, 1.0, FlexBasis::Auto
            )
    }
    
    add_todo(mut self) {
        if !self.new_todo_text.is_empty() {
            self.todos.push(Todo {
                id: generate_id(),
                text: self.new_todo_text.clone(),
                completed: false,
            })
            self.new_todo_text = ""
            self.request_update()
        }
    }
}

Widget 类型为 Valkyrie 提供了强大的 UI 开发能力,通过声明式的组件模型和响应式的状态管理,使得构建现代用户界面变得简单而高效。

Released under the MIT License.