1 // Written in the D programming language.
2 /++
3  + Authors: KanzakiKino
4  + Copyright: KanzakiKino 2018
5  + License: LGPL-3.0
6 ++/
7 module w4d.widget.input.checkbox;
8 import w4d.layout.placer.lineup,
9        w4d.layout.fill,
10        w4d.parser.colorset,
11        w4d.style.color,
12        w4d.style.rect,
13        w4d.style.scalar,
14        w4d.style.widget,
15        w4d.task.window,
16        w4d.widget.base,
17        w4d.widget.text,
18        w4d.event,
19        w4d.exception;
20 import g4d.element.shape.border,
21        g4d.element.shape.regular,
22        g4d.ft.font,
23        g4d.glfw.cursor,
24        g4d.shader.base;
25 import gl3n.linalg;
26 import std.math;
27 
28 /// A handler that handles changing checked.
29 alias CheckHandler = EventHandler!( void, bool );
30 
31 /// A widget of checkbox.
32 class CheckBoxWidget : Widget
33 {
34     protected class CheckMarkWidget : Widget
35     {
36         protected RegularNgonBorderElement!4 _border;
37         protected RegularNgonElement!4       _mark;
38 
39         override @property vec2 wantedSize ()
40         {
41             auto lineheight = _text.font.size.y;
42             return vec2( lineheight, lineheight );
43         }
44         override @property const(Cursor) cursor ()
45         {
46             return Cursor.Hand;
47         }
48         this ()
49         {
50             super();
51 
52             _border = new RegularNgonBorderElement!4;
53             _mark   = new RegularNgonElement!4;
54 
55             style.box.size.width  = Scalar.Auto;
56             style.box.size.height = Scalar.Auto;
57         }
58         override vec2 layout ( vec2 pos, vec2 size )
59         {
60             scope(success) {
61                 auto sz = style.box.clientSize.x;
62                 _border.resize( sz/2, 1.5f );
63                 _mark  .resize( sz/3 );
64             }
65             return super.layout( pos, size );
66         }
67         override void draw ( Window w, in ColorSet parent )
68         {
69             super.draw( w, parent );
70 
71             auto  shader = w.shaders.fill3;
72             const saver  = ShaderStateSaver( shader );
73             auto  late   = style.box.clientSize/2;
74             late        += style.clientLeftTop;
75 
76             shader.use();
77             shader.matrix.late = vec3( late, 0 );
78             shader.matrix.rota = vec3( 0, 0, PI/4 );
79             shader.color = colorset.foreground;
80             _border.draw( shader );
81 
82             if ( checked ) {
83                 _mark.draw( shader );
84             }
85         }
86         override @property bool trackable () { return false; }
87         override @property bool focusable () { return false; }
88     }
89 
90     protected CheckMarkWidget _mark;
91     protected TextWidget      _text;
92 
93     ///
94     override @property Widget[] children ()
95     {
96         return [ _mark, _text ];
97     }
98 
99     protected bool _checked;
100     /// Checks if the checkbox is checked.
101     @property checked () { return _checked; }
102 
103     ///
104     CheckHandler onCheck;
105 
106     ///
107     override bool handleMouseButton ( MouseButton btn, bool status, vec2 pos )
108     {
109         if ( super.handleMouseButton( btn, status, pos ) ) return true;
110 
111         if ( !style.isPointInside(pos) ) return false;
112 
113         if ( btn == MouseButton.Left && !status ) {
114             setChecked( !_checked );
115             return true;
116         }
117         return false;
118     }
119 
120     ///
121     this ()
122     {
123         super();
124         setLayout!( FillLayout, HorizontalLineupPlacer );
125 
126         _mark = new CheckMarkWidget;
127         _text = new TextWidget;
128 
129         _checked = false;
130 
131         parseColorSetsFromFile!"colorset/checkbox.yaml"( style );
132         style.box.margins     = Rect(1.mm);
133         style.box.borderWidth = Rect(1.pixel);
134     }
135 
136     /// Changes checked.
137     void setChecked ( bool b )
138     {
139         const temp = _checked;
140         _checked   = b;
141 
142         if ( _checked != temp ) {
143             onCheck.call( _checked );
144 
145             (_checked? &enableState: &disableState)
146                 ( WidgetState.Selected );
147             requestRedraw();
148         }
149     }
150 
151     /// Changes text.
152     void loadText ( dstring text, FontFace face = null )
153     {
154         _text.loadText( text, face );
155     }
156 
157     ///
158     override const @property bool trackable () { return true; }
159     ///
160     override const @property bool focusable () { return true; }
161 }