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 Checkbox
Browse files Browse the repository at this point in the history
  • Loading branch information
yvt committed May 3, 2020
1 parent 9a1d93a commit d2daca7
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 6 deletions.
1 change: 1 addition & 0 deletions tcw3/assets/checkbox_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/checkbox_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/checkbox_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/checkbox_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.
23 changes: 18 additions & 5 deletions tcw3/examples/tcw3_widgets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use tcw3::{
ui::{
layouts::TableLayout,
theming,
views::{scrollbar::ScrollbarDragListener, Button, Entry, Label, Scrollbar, Spacer},
views::{
scrollbar::ScrollbarDragListener, Button, Checkbox, Entry, Label, Scrollbar, Spacer,
},
AlignFlags,
},
uicore::{ActionId, ActionStatus, HView, HWnd, HWndRef, WndListener},
Expand Down Expand Up @@ -125,12 +127,23 @@ fn main() {
let button = Button::new(style_manager);
button.set_caption("Please don't touch this button");

let checkbox = Checkbox::new(style_manager);
let checkbox = Rc::new(checkbox);
checkbox.set_caption("Milk");
{
let checkbox_weak = Rc::downgrade(&checkbox);
checkbox.subscribe_activated(Box::new(move |_| {
let checkbox = checkbox_weak.upgrade().unwrap();
checkbox.set_checked(!checkbox.checked());
}));
}

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

Expand Down
2 changes: 2 additions & 0 deletions tcw3/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub mod mixins {

pub mod views {
mod button;
mod checkbox;
mod entry;
mod label;
pub mod scrollbar;
Expand All @@ -25,6 +26,7 @@ pub mod views {
pub mod table;
pub use self::{
button::Button,
checkbox::Checkbox,
entry::{Entry, EntryCore},
label::Label,
scrollbar::Scrollbar,
Expand Down
4 changes: 4 additions & 0 deletions tcw3/src/ui/theming/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ bitflags! {
const SPLITTER = 1 << 10;
/// The element is a text entry widget.
const ENTRY = 1 << 11;
/// The element is a checkbox widget.
const CHECKBOX = 1 << 12;
/// The element is checked.
const CHECKED = 1 << 13;

/// The bit mask for ID values. See [`ClassSet::id`] for more.
const ID_MASK = 0xffff_0000;
Expand Down
69 changes: 68 additions & 1 deletion tcw3/src/ui/theming/stylesheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,22 +543,58 @@ macro_rules! stylesheet {
// TODO: Make it dynamic (based on the operating system's configuration)
//
use crate::{
images::{figures, himg_figures, himg_from_figures_with_size},
images::{figures, himg_figures, himg_from_figures_with_size, HImg},
pal::RGBAF32,
stvg::StvgImg,
};
use cggeom::box2;
use cgmath::Vector2;
use std::f32::NAN;

mod assets {
pub type Stvg = (&'static [u8], [f32; 2]);

macro_rules! stvg {
($path:literal) => {
stvg_macro::include_stvg!($path)
};
}

pub static CHECKBOX_LIGHT: Stvg = stvg!("assets/checkbox_light.svg");
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");
}

const BUTTON_CORNER_RADIUS: f32 = 2.0;

const CHECKBOX_IMG_SIZE: Vector2<f32> = Vector2::new(16.0, 16.0);

const SCROLLBAR_VISUAL_WIDTH: f32 = 6.0;
const SCROLLBAR_VISUAL_RADIUS: f32 = SCROLLBAR_VISUAL_WIDTH / 2.0;
const SCROLLBAR_MARGIN: f32 = 6.0;
const SCROLLBAR_LEN_MIN: f32 = 20.0;

const FIELD_HEIGHT: f32 = 20.0;

/// Replace blue with a global tint color, and create a `HImg`.
fn recolor_tint(data: &(&'static [u8], [f32; 2])) -> HImg {
fn lerp(a: f32, b: f32, fract: f32) -> f32 {
a * (1.0 - fract) + b * fract
}
fn map_color(c: RGBAF32) -> RGBAF32 {
let tint: RGBAF32 = [0.2, 0.5, 0.9, 1.0].into();
[
lerp(c.g, c.b, tint.r),
lerp(c.g, c.b, tint.g),
lerp(c.g, c.b, tint.b),
c.a,
]
.into()
}
StvgImg::new(*data).with_color_xform(map_color).into_himg()
}

lazy_static! {
static ref DEFAULT_STYLESHEET: StylesheetMacroOutput = stylesheet! {
([.BUTTON]) (priority = 100) {
Expand Down Expand Up @@ -621,6 +657,37 @@ lazy_static! {
fg_color: RGBAF32::new(0.0, 0.0, 0.0, 1.0),
},

([.CHECKBOX]) (priority = 100) {
num_layers: 1,
#[dyn] layer_img[0]: Some(recolor_tint(&assets::CHECKBOX_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()
},
},
([.CHECKBOX.HOVER]) (priority = 200) {
layer_opacity[0]: 1.0,
},
([.CHECKBOX.ACTIVE]) (priority = 200) {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::CHECKBOX_LIGHT_ACT)),
},
([.CHECKBOX.CHECKED]) (priority = 300) {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::CHECKBOX_LIGHT_CHECKED)),
},
([.CHECKBOX.ACTIVE.CHECKED]) (priority = 400) {
#[dyn] layer_img[0]: Some(recolor_tint(&assets::CHECKBOX_LIGHT_CHECKED_ACT)),
},

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

// Entry wrapper
([.ENTRY]) (priority = 100) {
num_layers: 2,
Expand Down
93 changes: 93 additions & 0 deletions tcw3/src/ui/views/checkbox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use crate::{
pal,
ui::{
theming::{ClassSet, HElem, Manager, Widget},
views::Button,
},
uicore::{HView, HViewRef, Sub},
};

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

impl Checkbox {
pub fn new(style_manager: &'static Manager) -> Self {
let button = Button::new(style_manager);

button.set_class_set(ClassSet::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()
}

/// 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 Checkbox {
fn view_ref(&self) -> HViewRef<'_> {
self.view_ref()
}

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

0 comments on commit d2daca7

Please sign in to comment.