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(position, ss.getImage("ElementDisabledPtrn"));
54 		}
55 		/+if(text) {
56 			const int textPadding = getAvailableStyleSheet.drawParameters["TextSpacingSides"];
57 			const Coordinate textPos = Coordinate(textPadding +	getAvailableStyleSheet().getImage(iconUnlatched).width,
58 					(position.height / 2) - (text.font.size / 2), position.width, position.height - textPadding);
59 			output.drawSingleLineText(textPos, text);
60 		}
61 		/+output.drawColorText(getAvailableStyleSheet().getImage("checkBoxA").width, 0, text,
62 				getAvailableStyleSheet().getFontset("default"), getAvailableStyleSheet().getColor("normaltext"), 0);+/
63 		if(_isLatched) {
64 			output.insertBitmap(0, 0, getAvailableStyleSheet().getImage(iconLatched));
65 		} else {
66 			output.insertBitmap(0, 0, getAvailableStyleSheet().getImage(iconUnlatched));
67 		}
68 		elementContainer.drawUpdate(this);
69 		if(onDraw !is null) {
70 			onDraw();
71 		}+/
72 	}
73 
74 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
75 		if (mce.button == MouseButton.Left && mce.state == ButtonState.Pressed) group.latch(this);
76 		super.passMCE(mec, mce);
77 	}
78 	/**
79 	 * If the radio button is pressed, then it sets to unpressed. Does nothing otherwise.
80 	 */
81 	public void latchOff() @trusted {
82 		if (isChecked) {
83 			isChecked = false;
84 			draw();	
85 		}
86 	}
87 	/**
88 	 * Sets the radio button into its pressed state.
89 	 */
90 	public void latchOn() @trusted {
91 		if (!isChecked) {
92 			isChecked = true;
93 			draw();
94 		}
95 	}
96 	
97 	/**
98 	 * Sets the group of the radio button.
99 	 */
100 	public void setGroup(IRadioButtonGroup group) @safe @property {
101 		this.group = group;
102 	}
103 	public bool equals(IRadioButton rhs) @safe pure @nogc nothrow const {
104 		WindowElement we = cast(WindowElement)rhs;
105 		return source == we.source;
106 	}
107 	public string value() @property @safe @nogc pure nothrow const {
108 		return source;
109 	}
110 	public bool isSmallButtonHeight(int height) {
111 		if (text) return false;
112 		else if (position.width == height && position.height == height) return true;
113 		else return false;
114 	}
115 	///Returns true if left side justified, false otherwise.
116 	public bool isLeftSide() @nogc @safe pure nothrow const {
117 		return flags & IS_LHS ? true : false;
118 	}
119 	///Sets the small button to the left side if true.
120 	public bool isLeftSide(bool val) @nogc @safe pure nothrow {
121 		if (val) flags |= IS_LHS;
122 		else flags &= ~IS_LHS;
123 		return flags & IS_LHS ? true : false;
124 	}
125 }
126 
127 /**
128  * Radio Button Group implementation.
129  * Can send events via it's delegates.
130  */
131 public class RadioButtonGroup : IRadioButtonGroup {
132 	alias RadioButtonSet = LinkedList!(IRadioButton, false, "a.equals(b)");
133 	protected RadioButtonSet	radioButtons;
134 	protected IRadioButton		latchedButton;
135 	protected size_t 			_latchPos;
136 	public EventDeleg			onToggle;		///If set, it'll be called when the group is toggled.
137 	///Empty ctor
138 	public this() @safe pure nothrow @nogc {
139 		
140 	}
141 	/**
142 	 * Creates a new group with some starting elements from a compatible range.
143 	 */
144 	public this(R)(R range) @safe {
145 		foreach (key; range) {
146 			radioButtons.put(key);
147 		}
148 	}
149 	/**
150 	 * Adds a new RadioButton to the group.
151 	 */
152 	public void add(IRadioButton rg) @safe {
153 		radioButtons.put(rg);
154 		rg.setGroup(this);
155 	}
156 	/**
157 	 * Removes the given RadioButton from the group.
158 	 */
159 	public void remove(IRadioButton rg) @safe {
160 		radioButtons.removeByElem(rg);
161 		rg.setGroup(null);
162 	}
163 	/**
164 	 * Latches the group.
165 	 */
166 	public void latch(IRadioButton sender) @safe {
167 		latchedButton = sender;
168 		for (int i ; i < radioButtons.length ; i++) {
169 			IRadioButton elem = radioButtons[i];
170 			if (sender.equals(elem)) {
171 				_latchPos = i;
172 			}
173 			elem.latchOff;
174 		}
175 		sender.latchOn;
176 		callOnToggle(new Event(this, cast(Object)sender, EventType.Toggle, SourceType.RadioButtonGroup));
177 	}
178 	/**
179 	 * Latches to the given position.
180 	 */
181 	public size_t latchPos(size_t val) @safe {
182 		foreach(elem; radioButtons) {
183 			elem.latchOff;
184 		}
185 		radioButtons[val].latchOn;
186 		_latchPos = val;
187 		callOnToggle(new Event(this, cast(Object)radioButtons[val], EventType.Toggle, SourceType.RadioButtonGroup));
188 		return _latchPos;
189 	}
190 	///Calls the `onToggle` delegate if set
191 	protected void callOnToggle(Event ev) @trusted {
192 		if (onToggle !is null)
193 			onToggle (ev);
194 	}
195 	/**
196 	 * Returns the current latch position.
197 	 */
198 	public size_t latchPos() @nogc @safe pure nothrow {
199 		return _latchPos;
200 	}
201 	/**
202 	 * Returns the value of this group.
203 	 */
204 	public @property string value() const @nogc @safe pure nothrow {
205 		if(latchedButton !is null) return latchedButton.value;
206 		else return null;
207 	}
208 }