Skip to content
This repository has been archived by the owner on May 12, 2022. It is now read-only.

Commit

Permalink
feat(ui): add RadioButton
Browse files Browse the repository at this point in the history
  • Loading branch information
yvt committed May 3, 2020
1 parent 4559b43 commit c636a51
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 67 deletions.
1 change: 1 addition & 0 deletions tcw3/assets/radio_light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tcw3/assets/radio_light_act.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tcw3/assets/radio_light_checked.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tcw3/assets/radio_light_checked_act.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 51 additions & 3 deletions tcw3/examples/tcw3_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use tcw3::{
layouts::TableLayout,
theming,
views::{
scrollbar::ScrollbarDragListener, Button, Checkbox, Entry, Label, Scrollbar, Spacer,
scrollbar::ScrollbarDragListener, Button, Checkbox, Entry, Label, RadioButton,
Scrollbar, Spacer,
},
AlignFlags,
},
Expand Down Expand Up @@ -138,11 +139,58 @@ fn main() {
}));
}

let v_layout1 = {
let view = HView::new(Default::default());
view.set_layout(TableLayout::stack_vert(vec![
(button.view(), AlignFlags::CENTER),
(checkbox.view(), AlignFlags::CENTER),
]));
view
};

let rbuttons = [
RadioButton::new(style_manager),
RadioButton::new(style_manager),
RadioButton::new(style_manager),
];
let rbuttons = Rc::new(rbuttons);
rbuttons[0].set_caption("Earth");
rbuttons[1].set_caption("Pegasi");
rbuttons[2].set_caption("Unicorn");
for i in 0..3 {
let rbuttons_weak = Rc::downgrade(&rbuttons);
rbuttons[i].subscribe_activated(Box::new(move |_| {
let rbuttons = rbuttons_weak.upgrade().unwrap();
for (j, b) in rbuttons.iter().enumerate() {
b.set_checked(i == j);
}
}));
}

let v_layout2 = {
let view = HView::new(Default::default());
view.set_layout(TableLayout::stack_vert(vec![
(
rbuttons[0].view(),
AlignFlags::VERT_JUSTIFY | AlignFlags::LEFT,
),
(
rbuttons[1].view(),
AlignFlags::VERT_JUSTIFY | AlignFlags::LEFT,
),
(
rbuttons[2].view(),
AlignFlags::VERT_JUSTIFY | AlignFlags::LEFT,
),
]));
view
};

let h_layout = {
let view = HView::new(Default::default());
view.set_layout(TableLayout::stack_horz(vec![
(button.view(), AlignFlags::VERT_JUSTIFY),
(checkbox.view(), AlignFlags::CENTER),
(v_layout1, AlignFlags::VERT_JUSTIFY),
(v_layout2, AlignFlags::VERT_JUSTIFY),
]));
view
};
Expand Down
2 changes: 1 addition & 1 deletion tcw3/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub mod views {
pub mod table;
pub use self::{
button::Button,
checkbox::Checkbox,
checkbox::{Checkbox, RadioButton},
entry::{Entry, EntryCore},
label::Label,
scrollbar::Scrollbar,
Expand Down
2 changes: 2 additions & 0 deletions tcw3/src/ui/theming/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ bitflags! {
const CHECKBOX = 1 << 12;
/// The element is checked.
const CHECKED = 1 << 13;
/// The element is a radio button widget.
const RADIO_BUTTON = 1 << 14;

/// The bit mask for ID values. See [`ClassSet::id`] for more.
const ID_MASK = 0xffff_0000;
Expand Down
36 changes: 36 additions & 0 deletions tcw3/src/ui/theming/stylesheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,11 @@ mod assets {
pub static CHECKBOX_LIGHT_ACT: Stvg = stvg!("assets/checkbox_light_act.svg");
pub static CHECKBOX_LIGHT_CHECKED: Stvg = stvg!("assets/checkbox_light_checked.svg");
pub static CHECKBOX_LIGHT_CHECKED_ACT: Stvg = stvg!("assets/checkbox_light_checked_act.svg");

pub static RADIO_LIGHT: Stvg = stvg!("assets/radio_light.svg");
pub static RADIO_LIGHT_ACT: Stvg = stvg!("assets/radio_light_act.svg");
pub static RADIO_LIGHT_CHECKED: Stvg = stvg!("assets/radio_light_checked.svg");
pub static RADIO_LIGHT_CHECKED_ACT: Stvg = stvg!("assets/radio_light_checked_act.svg");
}

const BUTTON_CORNER_RADIUS: f32 = 2.0;
Expand Down Expand Up @@ -664,6 +669,7 @@ lazy_static! {
fg_color: RGBAF32::new(0.0, 0.0, 0.0, 1.0),
},

// Checkbox
([.CHECKBOX]) (priority = 100) {
num_layers: 1,
#[dyn] layer_img[0]: Some(recolor_tint(&assets::CHECKBOX_LIGHT)),
Expand All @@ -690,10 +696,40 @@ lazy_static! {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::CHECKBOX_LIGHT_CHECKED_ACT)),
},

// Radio button (identical to checkbox except for images)
([.RADIO_BUTTON]) (priority = 100) {
num_layers: 1,
#[dyn] layer_img[0]: Some(recolor_tint(&assets::RADIO_LIGHT)),
layer_metrics[0]: Metrics {
margin: [NAN, NAN, NAN, 4.0],
size: CHECKBOX_IMG_SIZE,
},
layer_opacity[0]: 0.9,
subview_metrics[Role::Generic]: Metrics {
margin: [3.0, 8.0, 3.0, 10.0 + CHECKBOX_IMG_SIZE.x],
.. Metrics::default()
},
},
([.RADIO_BUTTON.HOVER]) (priority = 200) {
layer_opacity[0]: 1.0,
},
([.RADIO_BUTTON.ACTIVE]) (priority = 200) {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::RADIO_LIGHT_ACT)),
},
([.RADIO_BUTTON.CHECKED]) (priority = 300) {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::RADIO_LIGHT_CHECKED)),
},
([.RADIO_BUTTON.ACTIVE.CHECKED]) (priority = 400) {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::RADIO_LIGHT_CHECKED_ACT)),
},

// Checkbox label
([] < [.CHECKBOX]) (priority = 100) {
fg_color: RGBAF32::new(0.0, 0.0, 0.0, 1.0),
},
([] < [.RADIO_BUTTON]) (priority = 100) {
fg_color: RGBAF32::new(0.0, 0.0, 0.0, 1.0),
},

// Entry wrapper
([.ENTRY]) (priority = 100) {
Expand Down
151 changes: 88 additions & 63 deletions tcw3/src/ui/views/checkbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ pub struct Checkbox {
button: Button,
}

/// A radio button widget (with a label).
#[derive(Debug)]
pub struct RadioButton {
button: Button,
}

impl Checkbox {
pub fn new(style_manager: &'static Manager) -> Self {
let button = Button::new(style_manager);
Expand All @@ -21,73 +27,92 @@ impl Checkbox {

Self { button }
}
}

/// Get an owned handle to the view representing the widget.
pub fn view(&self) -> HView {
self.button.view()
}

/// Borrow the handle to the view representing the widget.
pub fn view_ref(&self) -> HViewRef<'_> {
self.button.view_ref()
}

/// Get the styling element representing the widget.
pub fn style_elem(&self) -> HElem {
self.button.style_elem()
}

/// Set the text displayed in the widget.
pub fn set_caption(&self, value: impl Into<String>) {
self.button.set_caption(value);
}

/// Set the class set of the inner `StyledBox`.
///
/// It defaults to `ClassSet::CHECKBOX`. Some bits (e.g., `ACTIVE` and
/// `CHECKED`) are internally enforced and cannot be modified.
pub fn set_class_set(&self, mut class_set: ClassSet) {
// Protected bits
let protected = ClassSet::CHECKED;
class_set -= protected;
class_set |= self.button.class_set() & protected;

self.button.set_class_set(class_set);
}

/// Get the class set of the inner `StyledBox`.
pub fn class_set(&self) -> ClassSet {
self.button.class_set()
}
impl RadioButton {
pub fn new(style_manager: &'static Manager) -> Self {
let button = Button::new(style_manager);

/// Add a function called when the widget is activated.
///
/// The function is called via `Wm::invoke`, thus allowed to modify
/// view hierarchy and view attributes. However, it's not allowed to call
/// `subscribe_activated` when one of the handlers is being called.
pub fn subscribe_activated(&self, cb: Box<dyn Fn(pal::Wm)>) -> Sub {
self.button.subscribe_activated(cb)
}
button.set_class_set(ClassSet::RADIO_BUTTON);

/// Check or uncheck the checkbox.
pub fn set_checked(&self, value: bool) {
let mut class_set = self.button.class_set();
class_set.set(ClassSet::CHECKED, value);
self.button.set_class_set(class_set);
}

/// Get a flag indicating whether the checkbox is checked.
pub fn checked(&self) -> bool {
self.button.class_set().contains(ClassSet::CHECKED)
Self { button }
}
}

impl Widget for Checkbox {
fn view_ref(&self) -> HViewRef<'_> {
self.view_ref()
}

fn style_elem(&self) -> Option<HElem> {
Some(self.style_elem())
}
macro_rules! imp {
($t:ty) => {
impl $t {
/// Get an owned handle to the view representing the widget.
pub fn view(&self) -> HView {
self.button.view()
}

/// Borrow the handle to the view representing the widget.
pub fn view_ref(&self) -> HViewRef<'_> {
self.button.view_ref()
}

/// Get the styling element representing the widget.
pub fn style_elem(&self) -> HElem {
self.button.style_elem()
}

/// Set the text displayed in the widget.
pub fn set_caption(&self, value: impl Into<String>) {
self.button.set_caption(value);
}

/// Set the class set of the inner `StyledBox`.
///
/// Some bits (e.g., `ACTIVE` and `CHECKED`) are internally enforced
/// and cannot be modified.
pub fn set_class_set(&self, mut class_set: ClassSet) {
// Protected bits
let protected = ClassSet::CHECKED;
class_set -= protected;
class_set |= self.button.class_set() & protected;

self.button.set_class_set(class_set);
}

/// Get the class set of the inner `StyledBox`.
pub fn class_set(&self) -> ClassSet {
self.button.class_set()
}

/// Add a function called when the widget is activated.
///
/// The function is called via `Wm::invoke`, thus allowed to modify
/// view hierarchy and view attributes. However, it's not allowed to call
/// `subscribe_activated` when one of the handlers is being called.
pub fn subscribe_activated(&self, cb: Box<dyn Fn(pal::Wm)>) -> Sub {
self.button.subscribe_activated(cb)
}

/// Check or uncheck the checkbox.
pub fn set_checked(&self, value: bool) {
let mut class_set = self.button.class_set();
class_set.set(ClassSet::CHECKED, value);
self.button.set_class_set(class_set);
}

/// Get a flag indicating whether the checkbox is checked.
pub fn checked(&self) -> bool {
self.button.class_set().contains(ClassSet::CHECKED)
}
}

impl Widget for $t {
fn view_ref(&self) -> HViewRef<'_> {
self.view_ref()
}

fn style_elem(&self) -> Option<HElem> {
Some(self.style_elem())
}
}
};
}

imp!(Checkbox);
imp!(RadioButton);

0 comments on commit c636a51

Please sign in to comment.