use glyphon::Metrics;
use glyphon::cosmic_text::Align;
use taffy::prelude::*;
use winit::event::{ElementState, KeyEvent, MouseButton, WindowEvent};
use winit::keyboard::{Key, ModifiersState, NamedKey};
use crate::framework::{DrawContext, EventContext, Widget};
use crate::signal::{Signal, SetSignal};
use crate::WidgetNode;
pub struct RadioButton {
label: String,
index: usize,
selected: Signal<usize>,
set_selected: SetSignal<usize>,
metrics: Metrics,
circle_size: f32,
gap: f32,
circle_bg: [f32; 4],
circle_border: [f32; 4],
dot_color: [f32; 4],
text_color: [u8; 3],
hover: bool,
focus: bool,
}
impl RadioButton {
pub fn new(
label: impl Into<String>,
index: usize,
selected: Signal<usize>,
set_selected: SetSignal<usize>,
metrics: Metrics,
) -> Self {
Self {
label: label.into(),
index,
selected,
set_selected,
metrics,
circle_size: 20.0,
gap: 8.0,
circle_bg: [0.16, 0.28, 0.38, 1.0],
circle_border: [0.4, 0.55, 0.7, 1.0],
dot_color: [0.20, 0.65, 0.85, 1.0],
text_color: [230, 230, 230],
hover: false,
focus: false,
}
}
pub fn with_circle_size(mut self, size: f32) -> Self {
self.circle_size = size;
self
}
pub fn with_gap(mut self, gap: f32) -> Self {
self.gap = gap;
self
}
pub fn with_colors(
mut self,
circle_bg: [f32; 4],
circle_border: [f32; 4],
dot_color: [f32; 4],
) -> Self {
self.circle_bg = circle_bg;
self.circle_border = circle_border;
self.dot_color = dot_color;
self
}
pub fn with_text_color(mut self, color: [u8; 3]) -> Self {
self.text_color = color;
self
}
fn select(&self) {
self.set_selected.set(self.index);
}
fn is_selected(&self) -> bool {
self.selected.get() == self.index
}
fn hit_test(&self, layout: &Layout, x: f32, y: f32) -> bool {
x >= layout.location.x
&& x <= layout.location.x + layout.size.width
&& y >= layout.location.y
&& y <= layout.location.y + layout.size.height
}
}
impl Widget for RadioButton {
fn style(&self) -> Style {
let height = self.circle_size.max(self.metrics.line_height) + 8.0;
Style {
size: Size {
width: Dimension::Auto,
height: Dimension::Length(height),
},
flex_shrink: 0.0,
..Default::default()
}
}
fn draw(&self, ctx: &mut DrawContext) {
let layout = ctx.layout;
let selected = self.is_selected();
let cx = layout.location.x + 4.0;
let cy = layout.location.y + (layout.size.height - self.circle_size) / 2.0;
let radius = self.circle_size / 2.0;
let border_w = if self.focus { 2.0 } else { 1.0 };
let border_c = if self.focus {
[0.3, 0.6, 0.9, 1.0]
} else {
self.circle_border
};
ctx.renderer.fill_rect_styled(
(cx, cy, self.circle_size, self.circle_size),
self.circle_bg,
radius,
border_w,
border_c,
);
if selected {
let dot_size = self.circle_size * 0.5;
let dot_x = cx + (self.circle_size - dot_size) / 2.0;
let dot_y = cy + (self.circle_size - dot_size) / 2.0;
ctx.renderer.fill_rect_rounded(
(dot_x, dot_y, dot_size, dot_size),
self.dot_color,
dot_size / 2.0,
);
}
let text_x = cx + self.circle_size + self.gap;
let text_y = layout.location.y + (layout.size.height - self.metrics.line_height) / 2.0;
let text_w = (layout.size.width - (text_x - layout.location.x)).max(0.0);
ctx.renderer.draw_text(
&self.label,
(text_x, text_y),
self.text_color,
(text_w, self.metrics.line_height),
self.metrics,
Align::Left,
);
}
fn handle_event(&mut self, ctx: &mut EventContext) -> bool {
let layout = ctx.layout;
let mut changed = false;
match ctx.event {
WindowEvent::CursorMoved { position, .. } => {
let over = self.hit_test(layout, position.x as f32, position.y as f32);
if over != self.hover {
self.hover = over;
changed = true;
}
}
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} => {
if self.hover && !self.is_selected() {
self.select();
changed = true;
}
}
_ => {}
}
changed
}
fn handle_key_event(&mut self, event: &KeyEvent, _modifiers: ModifiersState) -> bool {
if event.state != ElementState::Pressed {
return false;
}
match &event.logical_key {
Key::Named(NamedKey::Space) | Key::Named(NamedKey::Enter) => {
if !self.is_selected() {
self.select();
}
true
}
_ => false,
}
}
fn is_focusable(&self) -> bool {
true
}
fn set_focus(&mut self, focused: bool) {
self.focus = focused;
}
fn activate(&mut self) {
self.select();
}
}
pub fn radio_group(
options: &[&str],
selected: Signal<usize>,
set_selected: SetSignal<usize>,
metrics: Metrics,
) -> Vec<WidgetNode> {
options
.iter()
.enumerate()
.map(|(i, label)| {
WidgetNode::new(
RadioButton::new(*label, i, selected.clone(), set_selected.clone(), metrics),
vec![],
)
})
.collect()
}