I’ve been waiting to add a color picker to Feathers UI until I have the time to make sure that its architecture is robust enough to be properly customizable. There are so many variations of color pickers out there, and I generally try to make sure that Feathers UI has the proper flexibility to support as wide a variety as possible.
That being said, it isn’t very difficult at all to create a basic color picker out of the ListView
component in Feathers UI. I’m thinking of sort of color picker with a grid of color “swatches”, where clicking a swatch would select that color. That would need a simple custom item renderer to display the color as a square/rectangle, and TiledRowsLayout
or TiledRowsListLayout
would position those color swatches in a grid.
I needed something like this a project, so I created a simple custom component called SwatchColorPicker
. I’ll include the code below. Warning: It is mostly just hacked together, and it is not up to the level of polish I would require of a real component included with Feathers UI.
import feathers.controls.Button;
import feathers.controls.ListView;
import feathers.core.FeathersControl;
import feathers.data.ArrayCollection;
import feathers.data.ListViewItemState;
import feathers.events.FeathersEvent;
import feathers.events.ListViewEvent;
import feathers.layout.TiledRowsLayout;
import feathers.skins.RectangleSkin;
import feathers.utils.DisplayObjectRecycler;
import openfl.events.Event;
class SwatchColorPicker extends FeathersControl {
public function new() {
super();
}
private var _listView:ListView;
private var _colorsCollection:ArrayCollection<Dynamic>;
public var selectedColor(default, set):UInt = 0x000000;
private function set_selectedColor(value:UInt):UInt {
if (this.selectedColor == value) {
return this.selectedColor;
}
this.selectedColor = value;
this.setInvalid(DATA);
FeathersEvent.dispatch(this, Event.CHANGE);
return this.selectedColor;
}
private var _ignoreColorChanges = false;
override private function initialize():Void {
super.initialize();
this._colorsCollection = new ArrayCollection([
{color: 0xFF0000}, {color: 0xFF6600}, {color: 0xFFFF00}, {color: 0x00FF00}, {color: 0x00FFFF}, {color: 0x0000FF}, {color: 0xFF00FF},
{color: 0x880088}, {color: 0x884411}, {color: 0xFFFFFF}, {color: 0x808080}, {color: 0x000000},
]);
this._listView = new ListView();
this._listView.selectable = false;
this._listView.dataProvider = this._colorsCollection;
this._listView.itemRendererRecycler = DisplayObjectRecycler.withFunction(() -> {
var itemRenderer = new Button();
itemRenderer.backgroundSkin = new RectangleSkin();
itemRenderer.width = 20.0;
itemRenderer.height = 20.0;
return itemRenderer;
}, (itemRenderer:Button, state:ListViewItemState) -> {
var backgroundSkin = cast(itemRenderer.backgroundSkin, RectangleSkin);
backgroundSkin.fill = SolidColor(state.data.color);
});
this._listView.addEventListener(ListViewEvent.ITEM_TRIGGER, itemTriggerHandler);
var listViewLayout = new TiledRowsLayout();
listViewLayout.requestedColumnCount = 6;
listViewLayout.setPadding(4.0);
listViewLayout.setGap(4.0);
this._listView.layout = listViewLayout;
this.addChild(this._listView);
}
override private function update():Void {
var dataInvalid = this.isInvalid(DATA);
if (dataInvalid) {
var oldIgnoreColorChanges = this._ignoreColorChanges;
this._ignoreColorChanges = true;
this._listView.selectedItem = this._colorsCollection.findIndex((item, index, collection) -> {
return item.color == this.selectedColor;
});
this._ignoreColorChanges = oldIgnoreColorChanges;
}
this._listView.validateNow();
this.saveMeasurements(this._listView.width, this._listView.height, this._listView.minWidth, this._listView.minHeight);
this._listView.x = 0.0;
this._listView.y = 0.0;
this._listView.width = this.actualWidth;
this._listView.height = this.actualHeight;
}
private function itemTriggerHandler(event:ListViewEvent):Void {
this.selectedColor = event.state.data.color;
}
}
I also created a variation where it’s more like a button, and clicking the currently selected color displays the swatches in a popup:
import feathers.controls.Callout;
import feathers.core.FeathersControl;
import feathers.events.FeathersEvent;
import feathers.skins.RectangleSkin;
import openfl.events.Event;
import openfl.events.MouseEvent;
class PopUpSwatchColorPicker extends FeathersControl {
public function new() {
super();
this.addEventListener(MouseEvent.CLICK, clickHandler);
}
private var _backgroundSkin:RectangleSkin;
private var _callout:Callout;
public var selectedColor(default, set):UInt = 0x000000;
private function set_selectedColor(value:UInt):UInt {
if (this.selectedColor == value) {
return this.selectedColor;
}
this.selectedColor = value;
this.setInvalid(DATA);
FeathersEvent.dispatch(this, Event.CHANGE);
return this.selectedColor;
}
override private function initialize():Void {
super.initialize();
this._backgroundSkin = new RectangleSkin(SolidColor(this.selectedColor), SolidColor(1.0, 0x000000));
this.addChild(this._backgroundSkin);
}
override private function update():Void {
var dataInvalid = this.isInvalid(DATA);
if (dataInvalid) {
this._backgroundSkin.fill = SolidColor(this.selectedColor);
}
this.saveMeasurements(20.0, 20.0, 20.0, 20.0);
this._backgroundSkin.x = 0.0;
this._backgroundSkin.y = 0.0;
this._backgroundSkin.width = this.actualWidth;
this._backgroundSkin.height = this.actualHeight;
}
private function clickHandler(event:MouseEvent):Void {
if (this._callout != null) {
return;
}
var swatches = new SwatchColorPicker();
swatches.selectedColor = this.selectedColor;
swatches.addEventListener(Event.CHANGE, swatchChangeHandler);
this._callout = Callout.show(swatches, this);
this._callout.addEventListener(Event.CLOSE, calloutCloseHandler);
}
private function swatchChangeHandler(event:Event):Void {
var swatches = cast(event.currentTarget, SwatchColorPicker);
this.selectedColor = swatches.selectedColor;
this._callout.close();
}
private function calloutCloseHandler(event:Event):Void {
this._callout = null;
}
}
Again, these are not as polished as normal components in Feathers UI, so there aren’t a lot of properties that you can customize (including which colors are displayed in the grid!). However, at least in the case of the colors, it’s easy to modify the code to include an expanded data provider of colors that you need for your app.