1 module pixelperfectengine.concrete.elements.radiobutton;
2 
3 public import pixelperfectengine.concrete.elements.base;
4 import collections.linkedlist;
5 
6 /**
7  * Implements a single radio button.
8  * Needs to be grouped to used.
9  * Equality is checked by comparing `source`, so give each RadioButton a different source value.
10  */
11 public class RadioButton : WindowElement, IRadioButton, ISmallButton {
12 	protected IRadioButtonGroup		group;		///The group which this object belongs to.
13 	//protected bool					_isLatched;		///The state of the RadioButton
14 	public string					iconLatched = "radioButtonB";	///Sets the icon for latched positions
15 	public string					iconUnlatched = "radioButtonA";	///Sets the icon for unlatched positions
16 	public this(Text text, string source, Box position, IRadioButtonGroup group = null) {
17 		this.position = position;
18 		this.text = text;
19 		this.source = source;
20 		if (group) group.add(this);
21 	}
22 	///Ditto
23 	public this(dstring text, string source, Box position, IRadioButtonGroup group = null) {
24 		this(new Text(text, getStyleSheet().getChrFormatting("checkBox")), source, position, group);
25 	}
26 	///Ditto
27 	public this(string iconLatched, string iconUnlatched, string source, Box position, IRadioButtonGroup group = null) {
28 		this.position = position;
29 		this.iconLatched = iconLatched;
30 		this.iconUnlatched = iconUnlatched;
31 		this.source = source;
32 		this.group = group;
33 		if (group)
34 			group.add(this);
35 	}
36 	override public void draw() {
37 		parent.clearArea(position);
38 		StyleSheet ss = getStyleSheet();
39 		Bitmap8Bit icon = isChecked ? ss.getImage(iconLatched) : ss.getImage(iconUnlatched);
40 		parent.bitBLT(position.cornerUL, icon);
41 		if (text) {
42 			Box textPos = position;
43 			textPos.left += icon.width + getStyleSheet.drawParameters["TextSpacingSides"];
44 			parent.drawTextSL(textPos, text, Point.init);
45 		}
46 
47 		if (isFocused) {
48 			const int textPadding = ss.drawParameters["horizTextPadding"];
49 			parent.drawBoxPattern(position - textPadding, ss.pattern["blackDottedLine"]);
50 		}
51 
52 		if (state == ElementState.Disabled) {
53 			parent.bitBLTPattern(Box(position.left, position.top, position.left + icon.width - 1, position.top + icon.height - 1
54 					), ss.getImage("ElementDisabledPtrn"));
55 		}
56 		if (onDraw !is null) {
57 			onDraw();
58 		}
59 	}
60 
61 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
62 		if (state != ElementState.Enabled) return;
63 		if (mce.button == MouseButton.Left && mce.state == ButtonState.Pressed) group.latch(this);
64 		super.passMCE(mec, mce);
65 	}
66 	/**
67 	 * If the radio button is pressed, then it sets to unpressed. Does nothing otherwise.
68 	 */
69 	public void latchOff() @trusted {
70 		if (isChecked) {
71 			isChecked = false;
72 			draw();	
73 		}
74 	}
75 	/**
76 	 * Sets the radio button into its pressed state.
77 	 */
78 	public void latchOn() @trusted {
79 		if (!isChecked) {
80 			isChecked = true;
81 			draw();
82 		}
83 	}
84 	
85 	/**
86 	 * Sets the group of the radio button.
87 	 */
88 	public void setGroup(IRadioButtonGroup group) @safe @property {
89 		this.group = group;
90 	}
91 	public bool equals(IRadioButton rhs) @safe pure @nogc nothrow const {
92 		WindowElement we = cast(WindowElement)rhs;
93 		return source == we.source;
94 	}
95 	public string value() @property @safe @nogc pure nothrow const {
96 		return source;
97 	}
98 	public bool isSmallButtonHeight(int height) {
99 		if (text) return false;
100 		else if (position.width == height && position.height == height) return true;
101 		else return false;
102 	}
103 	///Returns true if left side justified, false otherwise.
104 	public bool isLeftSide() @nogc @safe pure nothrow const {
105 		return flags & IS_LHS ? true : false;
106 	}
107 	///Sets the small button to the left side if true.
108 	public bool isLeftSide(bool val) @nogc @safe pure nothrow {
109 		if (val) flags |= IS_LHS;
110 		else flags &= ~IS_LHS;
111 		return flags & IS_LHS ? true : false;
112 	}
113 }
114 
115 /**
116  * Radio Button Group implementation.
117  * Can send events via it's delegates.
118  */
119 public class RadioButtonGroup : IRadioButtonGroup {
120 	alias RadioButtonSet = LinkedList!(IRadioButton, false, "a is b");
121 	protected RadioButtonSet	radioButtons;
122 	protected IRadioButton		latchedButton;
123 	protected size_t 			_latchPos;
124 	public EventDeleg			onToggle;		///If set, it'll be called when the group is toggled.
125 	///Empty ctor
126 	public this() @safe pure nothrow @nogc {
127 		
128 	}
129 	/**
130 	 * Creates a new group with some starting elements from a compatible range.
131 	 */
132 	public this(R)(R range) @safe {
133 		foreach (key; range) {
134 			synchronized
135 				add(key);
136 		}
137 	}
138 	/**
139 	 * Adds a new RadioButton to the group.
140 	 */
141 	public void add(IRadioButton rg) @safe {
142 		radioButtons.put(rg);
143 		rg.setGroup(this);
144 	}
145 	/**
146 	 * Removes the given RadioButton from the group.
147 	 */
148 	public void remove(IRadioButton rg) @safe {
149 		radioButtons.removeByElem(rg);
150 		rg.setGroup(null);
151 	}
152 	/**
153 	 * Latches the group.
154 	 */
155 	public void latch(IRadioButton sender) @safe {
156 		latchedButton = sender;
157 		for (int i ; i < radioButtons.length ; i++) {
158 			IRadioButton elem = radioButtons[i];
159 			if (sender.equals(elem)) {
160 				_latchPos = i;
161 			}
162 			elem.latchOff;
163 		}
164 		sender.latchOn;
165 		callOnToggle(new Event(this, cast(Object)sender, EventType.Toggle, SourceType.RadioButtonGroup));
166 	}
167 	/**
168 	 * Latches to the given position.
169 	 */
170 	public size_t latchPos(size_t val) @safe {
171 		foreach(elem; radioButtons) {
172 			elem.latchOff;
173 		}
174 		radioButtons[val].latchOn;
175 		_latchPos = val;
176 		callOnToggle(new Event(this, cast(Object)radioButtons[val], EventType.Toggle, SourceType.RadioButtonGroup));
177 		return _latchPos;
178 	}
179 	///Calls the `onToggle` delegate if set
180 	protected void callOnToggle(Event ev) @trusted {
181 		if (onToggle !is null)
182 			onToggle (ev);
183 	}
184 	/**
185 	 * Returns the current latch position.
186 	 */
187 	public size_t latchPos() @nogc @safe pure nothrow {
188 		return _latchPos;
189 	}
190 	/**
191 	 * Returns the value of this group.
192 	 */
193 	public @property string value() const @nogc @safe pure nothrow {
194 		if(latchedButton !is null) return latchedButton.value;
195 		else return null;
196 	}
197 }