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     /// Checks if the checkbox is checked.
100     const @property checked () { return status.selected; }
101 
102     ///
103     CheckHandler onCheck;
104 
105     ///
106     override bool handleMouseButton ( MouseButton btn, bool status, vec2 pos )
107     {
108         if ( super.handleMouseButton( btn, status, pos ) ) return true;
109 
110         if ( !style.isPointInside(pos) ) return false;
111 
112         if ( btn == MouseButton.Left && !status ) {
113             setChecked( !checked );
114             return true;
115         }
116         return false;
117     }
118 
119     ///
120     this ()
121     {
122         super();
123         setLayout!( FillLayout, HorizontalLineupPlacer );
124 
125         _mark = new CheckMarkWidget;
126         _text = new TextWidget;
127 
128         parseColorSetsFromFile!"colorset/checkbox.yaml"( style );
129         style.box.margins     = Rect(1.mm);
130         style.box.borderWidth = Rect(1.pixel);
131     }
132 
133     /// Changes checked.
134     void setChecked ( bool b )
135     {
136         if ( status.locked ) return;
137 
138         const temp      = status.selected;
139         status.selected = b;
140 
141         if ( b != temp ) {
142             onCheck.call( b );
143         }
144     }
145 
146     /// Changes text.
147     void loadText ( dstring text, FontFace face = null )
148     {
149         _text.loadText( text, face );
150     }
151 
152     ///
153     override const @property bool trackable () { return true; }
154     ///
155     override const @property bool focusable () { return true; }
156 }