Skip to content

Commit

Permalink
GridViewColumn, TreeGridViewColumn: support multiple cellRendererRecy…
Browse files Browse the repository at this point in the history
…clers in the same column
  • Loading branch information
joshtynjala committed Mar 6, 2025
1 parent ac127a4 commit 85f5675
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 20 deletions.
80 changes: 80 additions & 0 deletions src/feathers/controls/GridViewColumn.hx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ package feathers.controls;
import feathers.data.GridViewCellState;
import feathers.data.SortOrder;
import feathers.utils.AbstractDisplayObjectRecycler;
import feathers.utils.DisplayObjectRecycler;
import openfl.display.DisplayObject;
import openfl.events.EventDispatcher;

Expand All @@ -22,20 +23,26 @@ import openfl.events.EventDispatcher;
@since 1.0.0
**/
class GridViewColumn extends EventDispatcher implements IGridViewColumn {
private static var CURRENT_COLUMN_ID = 0;

/**
Creates a new `GridViewColumn` object with the given arguments.
@since 1.0.0
**/
public function new(?headerText:String, ?itemToText:(Dynamic) -> String, ?width:Float) {
super();
this.__columnID = CURRENT_COLUMN_ID;
CURRENT_COLUMN_ID++;
this.headerText = headerText;
if (itemToText != null) {
this.itemToText = itemToText;
}
this.width = width;
}

@:noCompletion private var __columnID:Int;

/**
The text to display in the column's header.
Expand Down Expand Up @@ -166,4 +173,77 @@ class GridViewColumn extends EventDispatcher implements IGridViewColumn {
public dynamic function itemToText(data:Dynamic):String {
return Std.string(data);
}

private var _recyclerMap:Map<String, DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>> = null;

/**
Returns the item renderer recycler associated with a specific ID.
Returns `null` if no recycler is associated with the ID.
@see `GridViewColumn.cellRendererRecyclerIDFunction`
@see `GridViewColumn.setCellRendererRecycler()`
@since 1.4.0
**/
public function getCellRendererRecycler(id:String):DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject> {
if (this._recyclerMap == null) {
return null;
}
return this._recyclerMap.get(id);
}

/**
Associates a cell renderer recycler with an ID to allow multiple types
of cell renderers may be displayed in the grid view column. A custom
`cellRendererRecyclerIDFunction` may be specified to return the ID of
the recycler to use for a specific item in the data provider.
To clear a recycler, pass in `null` for the value.
@see `GridViewColumn.cellRendererRecyclerIDFunction`
@see `GridViewColumn.getCellRendererRecycler()`
@since 1.4.0
**/
public function setCellRendererRecycler(id:String, recycler:AbstractDisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>):Void {
if (this._recyclerMap == null) {
this._recyclerMap = [];
}
if (recycler == null) {
this._recyclerMap.remove(id);
return;
}
this._recyclerMap.set(id, recycler);
}

/**
When a grid view column requires multiple cell renderer types, this
function is used to determine which type of cell renderer is required
for a specific item. Returns the ID of the cell renderer recycler to use
for the item, or `null` if the default `cellRendererRecycler` should be
used.
The following example provides an `cellRendererRecyclerIDFunction`:
```haxe
var regularItemRecycler = DisplayObjectRecycler.withClass(HierarchicalItemRenderer);
var firstItemRecycler = DisplayObjectRecycler.withClass(MyCustomItemRenderer);
column.setCellRendererRecycler("regular-item", regularItemRecycler);
column.setCellRendererRecycler("first-item", firstItemRecycler);
column.cellRendererRecyclerIDFunction = function(state:GridViewCellState):String {
if(state.rowIndex == 0) {
return "first-item";
}
return "regular-item";
};
```
@default null
@see `GridViewColumn.setCellRendererRecycler()`
@see `GridViewColumn.itemRendererRecycler
@since 1.4.0
**/
public var cellRendererRecyclerIDFunction:(state:GridViewCellState) -> String;
}
80 changes: 80 additions & 0 deletions src/feathers/controls/TreeGridViewColumn.hx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package feathers.controls;

import feathers.data.TreeGridViewCellState;
import feathers.utils.AbstractDisplayObjectRecycler;
import feathers.utils.DisplayObjectRecycler;
import openfl.display.DisplayObject;
import openfl.events.EventDispatcher;

Expand All @@ -21,20 +22,26 @@ import openfl.events.EventDispatcher;
@since 1.0.0
**/
class TreeGridViewColumn extends EventDispatcher implements IGridViewColumn {
private static var CURRENT_COLUMN_ID = 0;

/**
Creates a new `TreeGridViewColumn` object with the given arguments.
@since 1.0.0
**/
public function new(?headerText:String, ?itemToText:(Dynamic) -> String, ?width:Float) {
super();
this.__columnID = CURRENT_COLUMN_ID;
CURRENT_COLUMN_ID++;
this.headerText = headerText;
if (itemToText != null) {
this.itemToText = itemToText;
}
this.width = width;
}

@:noCompletion private var __columnID:Int;

/**
The text to display in the column's header.
Expand Down Expand Up @@ -119,4 +126,77 @@ class TreeGridViewColumn extends EventDispatcher implements IGridViewColumn {
public dynamic function itemToText(data:Dynamic):String {
return Std.string(data);
}

private var _recyclerMap:Map<String, DisplayObjectRecycler<Dynamic, TreeGridViewCellState, DisplayObject>> = null;

/**
Returns the item renderer recycler associated with a specific ID.
Returns `null` if no recycler is associated with the ID.
@see `TreeGridViewColumn.cellRendererRecyclerIDFunction`
@see `TreeGridViewColumn.setCellRendererRecycler()`
@since 1.4.0
**/
public function getCellRendererRecycler(id:String):DisplayObjectRecycler<Dynamic, TreeGridViewCellState, DisplayObject> {
if (this._recyclerMap == null) {
return null;
}
return this._recyclerMap.get(id);
}

/**
Associates a cell renderer recycler with an ID to allow multiple types
of cell renderers may be displayed in the grid view column. A custom
`cellRendererRecyclerIDFunction` may be specified to return the ID of
the recycler to use for a specific item in the data provider.
To clear a recycler, pass in `null` for the value.
@see `TreeGridViewColumn.cellRendererRecyclerIDFunction`
@see `TreeGridViewColumn.getCellRendererRecycler()`
@since 1.4.0
**/
public function setCellRendererRecycler(id:String, recycler:AbstractDisplayObjectRecycler<Dynamic, TreeGridViewCellState, DisplayObject>):Void {
if (this._recyclerMap == null) {
this._recyclerMap = [];
}
if (recycler == null) {
this._recyclerMap.remove(id);
return;
}
this._recyclerMap.set(id, recycler);
}

/**
When a grid view column requires multiple cell renderer types, this
function is used to determine which type of cell renderer is required
for a specific item. Returns the ID of the cell renderer recycler to use
for the item, or `null` if the default `cellRendererRecycler` should be
used.
The following example provides an `cellRendererRecyclerIDFunction`:
```haxe
var regularItemRecycler = DisplayObjectRecycler.withClass(HierarchicalItemRenderer);
var firstItemRecycler = DisplayObjectRecycler.withClass(MyCustomItemRenderer);
column.setCellRendererRecycler("regular-item", regularItemRecycler);
column.setCellRendererRecycler("first-item", firstItemRecycler);
column.cellRendererRecyclerIDFunction = function(state:TreeGridViewCellState):String {
if(state.rowIndex == 0) {
return "first-item";
}
return "regular-item";
};
```
@default null
@see `TreeGridViewColumn.setCellRendererRecycler()`
@see `TreeGridViewColumn.itemRendererRecycler
@since 1.4.0
**/
public var cellRendererRecyclerIDFunction:(state:TreeGridViewCellState) -> String;
}
56 changes: 47 additions & 9 deletions src/feathers/controls/dataRenderers/GridViewRowRenderer.hx
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,20 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
}
}
}
var recyclerMap = @:privateAccess column._recyclerMap;
if (recyclerMap != null) {
for (recycler in recyclerMap) {
if (recycler.update == null) {
if (recycler.update == null) {
recycler.update = defaultUpdateCellRenderer;
// don't replace reset if we didn't replace update too
if (recycler.reset == null) {
recycler.reset = defaultResetCellRenderer;
}
}
}
}
}
}

this.refreshInactiveCellRenderers(this._defaultStorage, false);
Expand Down Expand Up @@ -598,9 +612,14 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
var column = this._columns.get(i);
var cellRenderer = this._columnToCellRenderer.get(column);
if (cellRenderer != null) {
var storage = this.cellRendererRecyclerToStorage(column.cellRendererRecycler);
var state = this._cellRendererToCellState.get(cellRenderer);
var changed = this.populateCurrentItemState(column, i, state, this._forceCellStateUpdate);
var oldRecyclerID = state.recyclerID;
var storage = this.cellStateToStorage(state);
if (storage.id != oldRecyclerID) {
this._unrenderedData.push(i);
continue;
}
if (changed) {
this.updateCellRenderer(cellRenderer, state, storage);
}
Expand All @@ -622,10 +641,28 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
}
}

private function cellRendererRecyclerToStorage(recycler:DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>):CellRendererStorage {
private function cellStateToStorage(state:GridViewCellState):CellRendererStorage {
var column = state.column;
var recyclerID:String = null;
if (column.cellRendererRecyclerIDFunction != null) {
recyclerID = column.cellRendererRecyclerIDFunction(state);
}
var recycler = column.cellRendererRecycler;
if (recyclerID != null) {
var recyclerMap = @:privateAccess column._recyclerMap;
if (recyclerMap != null) {
recycler = recyclerMap.get(recyclerID);
}
if (recycler == null) {
throw new IllegalOperationError('Cell renderer recycler ID "${recyclerID}" is not registered.');
}
}
if (recycler == null) {
return this._defaultStorage;
}
if (recyclerID == null) {
recyclerID = "__gridView_recycler_" + @:privateAccess column.__columnID;
}
if (this._additionalStorage == null) {
this._additionalStorage = [];
}
Expand All @@ -635,7 +672,7 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
return storage;
}
}
var storage = new CellRendererStorage(recycler);
var storage = new CellRendererStorage(recyclerID, recycler);
this._additionalStorage.push(storage);
return storage;
}
Expand Down Expand Up @@ -695,9 +732,8 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
}

private function createCellRenderer(state:GridViewCellState):DisplayObject {
var column = state.column;
var cellRenderer:DisplayObject = null;
var storage = this.cellRendererRecyclerToStorage(column.cellRendererRecycler);
var storage = this.cellStateToStorage(state);
if (storage.inactiveCellRenderers.length == 0) {
cellRenderer = storage.cellRendererRecycler.create();
if ((cellRenderer is IVariantStyleObject)) {
Expand All @@ -722,7 +758,6 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
} else {
cellRenderer = storage.inactiveCellRenderers.shift();
}
var storage = this.cellRendererRecyclerToStorage(column.cellRendererRecycler);
this.updateCellRenderer(cellRenderer, state, storage);
if ((cellRenderer is ITriggerView)) {
// prefer TriggerEvent.TRIGGER
Expand All @@ -741,7 +776,7 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
cellRenderer.addEventListener(Event.RESIZE, gridViewRowRenderer_cellRenderer_resizeHandler);
}
this._cellRendererToCellState.set(cellRenderer, state);
this._columnToCellRenderer.set(column, cellRenderer);
this._columnToCellRenderer.set(state.column, cellRenderer);
storage.activeCellRenderers.push(cellRenderer);
return cellRenderer;
}
Expand All @@ -762,12 +797,12 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
this.setInvalid(DATA);
return;
}
var storage = this.cellRendererRecyclerToStorage(column.cellRendererRecycler);
var state = this._cellRendererToCellState.get(cellRenderer);
if (state.owner == null) {
// a previous update is already pending
return;
}
var storage = this.cellStateToStorage(state);
this.populateCurrentItemState(column, columnIndex, state, true);
// in order to display the same item with modified properties, this
// hack tricks the item renderer into thinking that it has been given
Expand Down Expand Up @@ -817,6 +852,7 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
}

private function updateCellRenderer(cellRenderer:DisplayObject, state:GridViewCellState, storage:CellRendererStorage):Void {
state.recyclerID = storage.id;
var oldIgnoreSelectionChange = this._ignoreSelectionChange;
this._ignoreSelectionChange = true;
if (storage.cellRendererRecycler.update != null) {
Expand Down Expand Up @@ -1002,10 +1038,12 @@ class GridViewRowRenderer extends LayoutGroup implements ITriggerView implements
}

private class CellRendererStorage {
public function new(?recycler:DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>) {
public function new(?id:String, ?recycler:DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>) {
this.id = id;
this.cellRendererRecycler = recycler;
}

public var id:String;
public var oldCellRendererRecycler:DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>;
public var cellRendererRecycler:DisplayObjectRecycler<Dynamic, GridViewCellState, DisplayObject>;
public var activeCellRenderers:Array<DisplayObject> = [];
Expand Down
Loading

0 comments on commit 85f5675

Please sign in to comment.