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.equals(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 			radioButtons.put(key);
135 		}
136 	}
137 	/**
138 	 * Adds a new RadioButton to the group.
139 	 */
140 	public void add(IRadioButton rg) @safe {
141 		radioButtons.put(rg);
142 		rg.setGroup(this);
143 	}
144 	/**
145 	 * Removes the given RadioButton from the group.
146 	 */
147 	public void remove(IRadioButton rg) @safe {
148 		radioButtons.removeByElem(rg);
149 		rg.setGroup(null);
150 	}
151 	/**
152 	 * Latches the group.
153 	 */
154 	public void latch(IRadioButton sender) @safe {
155 		latchedButton = sender;
156 		for (int i ; i < radioButtons.length ; i++) {
157 			IRadioButton elem = radioButtons[i];
158 			if (sender.equals(elem)) {
159 				_latchPos = i;
160 			}
161 			elem.latchOff;
162 		}
163 		sender.latchOn;
164 		callOnToggle(new Event(this, cast(Object)sender, EventType.Toggle, SourceType.RadioButtonGroup));
165 	}
166 	/**
167 	 * Latches to the given position.
168 	 */
169 	public size_t latchPos(size_t val) @safe {
170 		foreach(elem; radioButtons) {
171 			elem.latchOff;
172 		}
173 		radioButtons[val].latchOn;
174 		_latchPos = val;
175 		callOnToggle(new Event(this, cast(Object)radioButtons[val], EventType.Toggle, SourceType.RadioButtonGroup));
176 		return _latchPos;
177 	}
178 	///Calls the `onToggle` delegate if set
179 	protected void callOnToggle(Event ev) @trusted {
180 		if (onToggle !is null)
181 			onToggle (ev);
182 	}
183 	/**
184 	 * Returns the current latch position.
185 	 */
186 	public size_t latchPos() @nogc @safe pure nothrow {
187 		return _latchPos;
188 	}
189 	/**
190 	 * Returns the value of this group.
191 	 */
192 	public @property string value() const @nogc @safe pure nothrow {
193 		if(latchedButton !is null) return latchedButton.value;
194 		else return null;
195 	}
196 }