From 3e96bba56e4aa009245ca89fe7c10252d4404422 Mon Sep 17 00:00:00 2001 From: Ian Harrigan Date: Sat, 10 Jun 2023 09:48:14 +0200 Subject: [PATCH] remove absurd class hierarchy --- haxe/ui/backend/ComponentBase.hx | 1691 ++++++++++++++++++++++++++- haxe/ui/core/Component.hx | 2 - haxe/ui/core/ComponentBounds.hx | 611 ---------- haxe/ui/core/ComponentCommon.hx | 45 - haxe/ui/core/ComponentContainer.hx | 371 ------ haxe/ui/core/ComponentEvents.hx | 308 ----- haxe/ui/core/ComponentLayout.hx | 26 - haxe/ui/core/ComponentValidation.hx | 315 ----- haxe/ui/macros/Macros.hx | 2 +- 9 files changed, 1641 insertions(+), 1730 deletions(-) delete mode 100644 haxe/ui/core/ComponentBounds.hx delete mode 100644 haxe/ui/core/ComponentCommon.hx delete mode 100644 haxe/ui/core/ComponentContainer.hx delete mode 100644 haxe/ui/core/ComponentEvents.hx delete mode 100644 haxe/ui/core/ComponentLayout.hx delete mode 100644 haxe/ui/core/ComponentValidation.hx diff --git a/haxe/ui/backend/ComponentBase.hx b/haxe/ui/backend/ComponentBase.hx index 3ec8ae0f4..a59b012ab 100644 --- a/haxe/ui/backend/ComponentBase.hx +++ b/haxe/ui/backend/ComponentBase.hx @@ -1,101 +1,1596 @@ package haxe.ui.backend; +import haxe.ui.backend.ComponentSurface; +import haxe.ui.behaviours.Behaviours; +import haxe.ui.events.UIEvent; +import haxe.ui.layouts.Layout; +import haxe.ui.styles.Style; +import haxe.ui.behaviours.DataBehaviour; +import haxe.ui.behaviours.ValueBehaviour; +import haxe.ui.util.Variant; +import haxe.ui.behaviours.DefaultBehaviour; +import haxe.ui.tooltips.ToolTipManager; +import haxe.ui.events.Events; +import haxe.ui.util.EventMap; +import haxe.ui.events.MouseEvent; +import haxe.ui.events.KeyboardEvent; +import haxe.ui.util.FunctionArray; +import haxe.ui.validation.InvalidationFlags; +import haxe.ui.validation.ValidationManager; +import haxe.ui.geom.Rectangle; +import haxe.ui.geom.Point; +import haxe.ui.core.IClonable; import haxe.ui.core.Component; -import haxe.ui.core.ComponentBounds; -import haxe.ui.core.ImageDisplay; import haxe.ui.core.TextDisplay; import haxe.ui.core.TextInput; -import haxe.ui.events.UIEvent; -import haxe.ui.geom.Point; -import haxe.ui.geom.Rectangle; -import haxe.ui.styles.Style; +import haxe.ui.core.ImageDisplay; +import haxe.ui.core.Screen; + +@:build(haxe.ui.macros.Macros.buildBehaviours()) +@:autoBuild(haxe.ui.macros.Macros.buildBehaviours()) +@:build(haxe.ui.macros.Macros.build()) +@:autoBuild(haxe.ui.macros.Macros.build()) +class ComponentBase extends ComponentSurface implements IClonable { + /** + * Creates a new `ComponentContainer`. + */ + public function new() { + super(); + behaviours = new Behaviours(cast(this, Component)); + } -@:dox(hide) @:noCompletion -class ComponentBase extends ComponentBounds { //*********************************************************************************************************** - // Default impl + // Behaviours //*********************************************************************************************************** - private function handleCreate(native:Bool) { + /** + * The text displayed inside this component. + */ + @:clonable @:behaviour(ComponentTextBehaviour) public var text:String; + + /** + * The text displayed inside the label. + * + * `value` is used as a universal way to access the "core" value a component is based on. + * in this case, its the component's text. + */ + @:clonable @:behaviour(ComponentValueBehaviour) public var value:Dynamic; + /** + * Whether to disable interactivity for this component or not. + * + * The user can't interact with disabled components, and they don't + * send state change events when the user tries to interact with them. + */ + @:clonable @:behaviour(ComponentDisabledBehaviour) public var disabled:Bool; + + /** + * A tooltip to display near the cursor when hovering on top of this component for a while. + * + * @see http://haxeui.org/explorer/#miscellaneous/tooltips + */ + @:clonable @:behaviour(ComponentToolTipBehaviour, null) public var tooltip:Dynamic; + + /** + * Some sort of a "default" tooltip layout used when assigning tooltips. + * Might be useful when you want to use complex tooltips. + * + * @see http://haxeui.org/explorer/#miscellaneous/tooltips + */ + @:clonable @:behaviour(ComponentToolTipRendererBehaviour, null) public var tooltipRenderer:Component; + + private var behaviours:Behaviours; + + private function registerBehaviours() {} + + //*********************************************************************************************************** + // General + //*********************************************************************************************************** + @:noCompletion private var _componentReady:Bool = false; + /** + Whether the framework considers this component ready or not. + **/ + public var isReady(get, null):Bool; + private function get_isReady():Bool { + return _componentReady; + } + + @:noCompletion private var _id:String = null; + /** + * This component's identifier. + */ + @:clonable public var id(get, set):String; + private function get_id():String { + return _id; + } + private function set_id(value:String):String { + if (_id != value) { + _id = value; + //invalidate(InvalidationFlags.STYLE); + //invalidateDisplay(); + } + return _id; } - private function handlePosition(left:Null, top:Null, style:Style) { + //*********************************************************************************************************** + // Display Tree + //*********************************************************************************************************** + /** + The parent component of this component instance. + + Returns `null` if this component hasn't been added yet, or just doesn't have a parent. + **/ + @:dox(group = "Display tree related properties and methods") + public var parentComponent:Component = null; + + @:noCompletion private var _children:Array; + /** + * An array of this component's children. + * + * Note: If this component has no children, and empty array is returned. + **/ + @:dox(group = "Display tree related properties and methods") + public var childComponents(get, null):Array; + private inline function get_childComponents():Array { + if (_children == null) { + return []; + } + return _children; + } + + /** + * Adds a component to the end of this component's display list. + * + * If this component already has children, the given component is added in front of the other children. + * + * @param child The child component to add to this component. + * @return The added component. + */ + public function addComponent(child:Component):Component { + return null; + } + + /** + * Inserts a child component after `index` children, effectively adding it "in front" of `index` children, and "behind" the rest. + * + * If `index` is below every other child's index, the added component will render behind this component's children. + * If `index` is above every other child's index, the added component will render in front of this component's children. + * + * For example, inserting a child into a `VBox` at `index = 0` will place it at the top, "behind" all other children + * + * @param child The child component to add to this component. + * @param index The index at which the child component should be added. + * @return The added component. + */ + public function addComponentAt(child:Component, index:Int):Component { + return null; + } + + /** + * Removes a child component from this component's display list, and returns it. + * + * @param child The child component to remove from this component. + * @param dispose Decides whether or not the child component should be destroyed too. + * @param invalidate If `true`, the child component updates itself after the removal. + * @return The removed child component + */ + public function removeComponent(child:Component, dispose:Bool = true, invalidate:Bool = true):Component { + return null; + } + + /** + * Removes the child component at index `index` from this component's display list, and returns it. + * + * @param index The index of the child component to remove from this component. + * @param dispose Decides whether or not the child component should be destroyed too. + * @param invalidate If `true`, the child component updates itself after the removal. + * @return The removed child component + */ + public function removeComponentAt(index:Int, dispose:Bool = true, invalidate:Bool = true):Component { + return null; + } + + /** + * Sets this component's z-index to `0`, + * effectively putting it behind every single one of the parent's children. + */ + public function moveComponentToBack() { + if (parentComponent == null || parentComponent.numComponents <= 1) { + return; + } + + parentComponent.setComponentIndex(cast this, 0); + } + + /** + * Moves this component behind the child component behind it. + */ + public function moveComponentBackward() { + if (parentComponent == null || parentComponent.numComponents <= 1) { + return; + } + + var index = parentComponent.getComponentIndex(cast this); + if (index == 0) { + return; + } + + parentComponent.setComponentIndex(cast this, index - 1); + } + + /** + * Sets this component's z-index to `parentComponent.numComponents - 1`, + * effectively putting it after in front of every single one of the parent's children. + */ + public function moveComponentToFront() { + if (parentComponent == null || parentComponent.numComponents <= 1) { + return; + } + + parentComponent.setComponentIndex(cast this, parentComponent.numComponents - 1); + } + + /** + * Moves this component to the front of the child component in front of it. + */ + public function moveComponentFrontward() { + if (parentComponent == null || parentComponent.numComponents <= 1) { + return; + } + + var index = parentComponent.getComponentIndex(cast this); + if (index == parentComponent.numComponents - 1) { + return; + } + + parentComponent.setComponentIndex(cast this, index + 1); + } + + /** + * Gets the child component at the bottom of this component + */ + public var bottomComponent(get, null):Component; + private function get_bottomComponent():Component { + if (_children == null || _children.length == 0) { + return null; + } + return cast _children[0]; + } + + /** + * Gets the child component at the top of this component + */ + public var topComponent(get, null):Component; + private function get_topComponent():Component { + if (_children == null || _children.length == 0) { + return null; + } + return cast _children[_children.length - 1]; + } + + private function postCloneComponent(c:Component):Void { + } + + //*********************************************************************************************************** + // Events + //*********************************************************************************************************** + @:noCompletion private var _internalEvents:Events = null; + @:noCompletion private var _internalEventsClass:Class = null; + private function registerInternalEvents(eventsClass:Class = null, reregister:Bool = false) { + if (_internalEvents == null && eventsClass != null) { + _internalEvents = Type.createInstance(eventsClass, [this]); + _internalEvents.register(); + } if (reregister == true && _internalEvents != null) { + _internalEvents.register(); + } + } + private function unregisterInternalEvents() { + if (_internalEvents == null) { + return; + } + _internalEvents.unregister(); + _internalEvents = null; } - private function handleSize(width:Null, height:Null, style:Style) { + @:noCompletion private var __events:EventMap; + + /** + Register a listener for a certain `UIEvent` + **/ + @:dox(group = "Event related properties and methods") + public function registerEvent(type:String, listener:T->Void, priority:Int = 0) { + if (cast(this, Component).hasClass(":mobile") && (type == MouseEvent.MOUSE_OVER || type == MouseEvent.MOUSE_OUT)) { + return; + } + + if (disabled == true && isInteractiveEvent(type) == true) { + if (_disabledEvents == null) { + _disabledEvents = new EventMap(); + } + _disabledEvents.add(type, listener, priority); + return; + } + + if (__events == null) { + __events = new EventMap(); + } + if (__events.add(type, listener, priority) == true) { + mapEvent(type, _onMappedEvent); + } + } + /** + Returns if this component has a certain event and listener + **/ + @:dox(group = "Event related properties and methods") + public function hasEvent(type:String, listener:T->Void = null):Bool { + if (__events == null) { + return false; + } + return __events.contains(type, listener); } - private function handleReady() { + /** + Unregister a listener for a certain `UIEvent` + **/ + @:dox(group = "Event related properties and methods") + public function unregisterEvent(type:String, listener:T->Void) { + if (_disabledEvents != null && !_interactivityDisabled) { + _disabledEvents.remove(type, listener); + } + if (__events != null) { + if (__events.remove(type, listener) == true) { + unmapEvent(type, _onMappedEvent); + } + } } - private function handleClipRect(value:Rectangle) { + /** + Dispatch a certain `UIEvent` + **/ + @:dox(group = "Event related properties and methods") + public function dispatch(event:UIEvent) { + if (event != null) { + if (__events != null) { + __events.invoke(event.type, event, cast(this, Component)); // TODO: avoid cast + } + if (event.bubble == true && event.canceled == false && parentComponent != null) { + parentComponent.dispatch(event); + } + } } - private function handleVisibility(show:Bool) { + private function dispatchRecursively(event:UIEvent) { + dispatch(event); + for (child in childComponents) { + child.dispatchRecursively(event); + } + } + private function dispatchRecursivelyWhen(event:UIEvent, condition:Component->Bool) { + if (condition(cast this) == true) { + dispatch(event); + } + for (child in childComponents) { + if (condition(child) == true) { + child.dispatchRecursivelyWhen(event, condition); + } + } + } + + @:noCompletion + private function _onMappedEvent(event:UIEvent) { + dispatch(event); } - private function getComponentOffset():Point { - return new Point(0, 0); + @:noCompletion private var _disabledEvents:EventMap; + private static var INTERACTIVE_EVENTS:Array = [ + MouseEvent.MOUSE_MOVE, MouseEvent.MOUSE_OVER, MouseEvent.MOUSE_OUT, MouseEvent.MOUSE_DOWN, + MouseEvent.MOUSE_UP, MouseEvent.MOUSE_WHEEL, MouseEvent.CLICK, MouseEvent.DBL_CLICK, KeyboardEvent.KEY_DOWN, + KeyboardEvent.KEY_UP + ]; + + private function isInteractiveEvent(type:String):Bool { + return INTERACTIVE_EVENTS.indexOf(type) != -1; } - private var isNativeScroller(get, null):Bool; - private function get_isNativeScroller():Bool { - return false; + private function disableInteractiveEvents(disable:Bool) { + if (disable == true) { + if (__events != null) { + for (eventType in __events.keys()) { + if (!isInteractiveEvent(eventType)) { + continue; + } + var listeners:FunctionArrayVoid> = __events.listeners(eventType); + if (listeners != null) { + for (listener in listeners.copy()) { + if (_disabledEvents == null) { + _disabledEvents = new EventMap(); + } + _disabledEvents.add(eventType, listener); + unregisterEvent(eventType, listener); + } + } + } + } + } else { + if (_disabledEvents != null) { + for (eventType in _disabledEvents.keys()) { + var listeners:FunctionArrayVoid> = _disabledEvents.listeners(eventType); + if (listeners != null) { + for (listener in listeners.copy()) { + registerEvent(eventType, listener); + } + } + } + _disabledEvents = null; + } + } } + + @:noCompletion private var _interactivityDisabled:Bool = false; + @:noCompletion private var _interactivityDisabledCounter:Int = 0; + #if haxeui_html5 + private var _lastCursor:String = null; + #end + private function disableInteractivity(disable:Bool, recursive:Bool = true, updateStyle:Bool = false, force:Bool = false) { // You might want to disable interactivity but NOT actually disable visually + if (force == true) { + _interactivityDisabledCounter = 0; + } + if (disable == true) { + _interactivityDisabledCounter++; + } else { + _interactivityDisabledCounter--; + } - private var isScroller(get, null):Bool; - private function get_isScroller():Bool { + if (_interactivityDisabledCounter > 0 && _interactivityDisabled == false) { + _interactivityDisabled = true; + if (updateStyle == true) { + cast(this, Component).swapClass(":disabled", ":hover"); + } + handleDisabled(true); + disableInteractiveEvents(true); + dispatch(new UIEvent(UIEvent.DISABLED)); + #if haxeui_html5 + _lastCursor = cast(this, Component).element.style.cursor; + cast(this, Component).element.style.removeProperty("cursor"); + #end + } else if (_interactivityDisabledCounter < 1 && _interactivityDisabled == true) { + _interactivityDisabled = false; + if (updateStyle == true) { + cast(this, Component).removeClass(":disabled"); + } + handleDisabled(false); + disableInteractiveEvents(false); + dispatch(new UIEvent(UIEvent.ENABLED)); + #if haxeui_html5 + if (_lastCursor != null) { + cast(this, Component).element.style.cursor = _lastCursor; + } + #end + } + + if (recursive == true) { + for (child in childComponents) { + child.disableInteractivity(disable, recursive, updateStyle); + } + } + } + + private function unregisterEvents() { + if (__events != null) { + var copy:Array = []; + for (eventType in __events.keys()) { + copy.push(eventType); + } + for (eventType in copy) { + var listeners = __events.listeners(eventType); + if (listeners != null) { + for (listener in listeners) { + if (listener != null) { + if (__events.remove(eventType, listener) == true) { + unmapEvent(eventType, _onMappedEvent); + } + } + } + } + } + } + } + + @:noCompletion private var _pausedEvents:MapVoid>> = null; + public function pauseEvent(type:String, recursive:Bool = false) { + if (__events == null || __events.contains(type) == false) { + return; + } + + if (_pausedEvents == null) { + _pausedEvents = new MapVoid>>(); + } + + var pausedList = _pausedEvents.get(type); + if (pausedList == null) { + pausedList = new ArrayVoid>(); + _pausedEvents.set(type, pausedList); + } + + var listeners = __events.listeners(type).copy(); + for (l in listeners) { + pausedList.push(l); + unregisterEvent(type, l); + } + + if (recursive == true) { + for (c in childComponents) { + c.pauseEvent(type, recursive); + } + } + } + + public function resumeEvent(type:String, recursive:Bool = false) { + if (__events == null) { + return; + } + + if (_pausedEvents == null) { + return; + } + + if (_pausedEvents.exists(type) == false) { + return; + } + + Toolkit.callLater(function() { + var pausedList = _pausedEvents.get(type); + for (l in pausedList) { + registerEvent(type, l); + } + _pausedEvents.remove(type); + }); + + if (recursive == true) { + for (c in childComponents) { + c.resumeEvent(type, recursive); + } + } + } + + //*********************************************************************************************************** + // Layout related + //*********************************************************************************************************** + // not idea place for them, but ComponentValidation needs them + @:noCompletion private var _layout:Layout = null; + + @:noCompletion private var _layoutLocked:Bool = false; + + //*********************************************************************************************************** + // Size related + //*********************************************************************************************************** + + /** + * When enabled, this component will automatically resize itself based on it's children's calculated width. + * + * For example, if this component's padding is `5`, and it has one child, `150` pixels wide, + * and `autoWidth` is set to `true`, this component's width should be `160` + */ + @:dox(group = "Size related properties and methods") + public var autoWidth(get, null):Bool; + private function get_autoWidth():Bool { + if (_percentWidth != null || _width != null) { + return false; + } + + if (style == null) { + return true; + } + + if (style.autoWidth == null) { + return false; + } + return style.autoWidth; + } + + /** + * When enabled, this component will automatically resize itself based on it's children's calculated height. + * + * For example, if this component's padding is `5`, and it has one child, `200` pixels tall, + * and `autoHeight` is set to `true`, this component's height should be `210` + */ + @:dox(group = "Size related properties and methods") + public var autoHeight(get, null):Bool; + private function get_autoHeight():Bool { + if (_percentHeight != null || _height != null || style == null) { + return false; + } + if (style.autoHeight == null) { + return false; + } + return style.autoHeight; + } + + /** + * Resizes a component to be `w` pixels wide and `h` pixels tall. + * + * Useful if you want to resize the component in both the X & Y axis, + * and don't want to call the resizing logic twice. + * @param w The component's new width. + * @param h The component's new height. + */ + @:dox(group = "Size related properties and methods") + public function resizeComponent(w:Null, h:Null) { + var invalidate:Bool = false; + + if (w != null && _componentWidth != w) { + _componentWidth = w; + invalidate = true; + } + + if (h != null && _componentHeight != h) { + _componentHeight = h; + invalidate = true; + } + + if (invalidate == true && isComponentInvalid(InvalidationFlags.LAYOUT) == false) { + invalidateComponentLayout(); + } + } + + /** + * The component's true width on screen. + * + * May differ from `componentWidth` if `Toolkit.scaleX != 1` + */ + public var actualComponentWidth(get, null):Float; + private function get_actualComponentWidth():Float { + return componentWidth * Toolkit.scaleX; + } + + /** + * The component's true height on screen. + * + * May differ from `componentHeight` if `Toolkit.scaleY != 1` + */ + public var actualComponentHeight(get, null):Float; + private function get_actualComponentHeight():Float { + return componentHeight * Toolkit.scaleY; + } + + @:noCompletion private var _componentWidth:Null; + @:allow(haxe.ui.layouts.Layout) + @:allow(haxe.ui.core.Screen) + + /** + * This component's calculated width. + */ + @:dox(group = "Size related properties and methods") + @:clonable private var componentWidth(get, set):Null; + private function get_componentWidth():Null { + if (_componentWidth == null) { + return 0; + } + return _componentWidth; + } + private function set_componentWidth(value:Null):Null { + resizeComponent(value, null); + return value; + } + + @:noCompletion private var _componentHeight:Null; + @:allow(haxe.ui.layouts.Layout) + @:allow(haxe.ui.core.Screen) + + /** + * This component's calculated height. + */ + @:dox(group = "Size related properties and methods") + @:clonable private var componentHeight(get, set):Null; + private function get_componentHeight():Null { + if (_componentHeight == null) { + return 0; + } + return _componentHeight; + } + private function set_componentHeight(value:Null):Null { + resizeComponent(null, value); + return value; + } + + @:noCompletion private var _percentWidth:Null; + + /** + * When set, sets this component's width to be `percentWidth`% percent of it's parent's width. + */ + @:dox(group = "Size related properties and methods") + @clonable @bindable public var percentWidth(get, set):Null; + private function get_percentWidth():Null { + return _percentWidth; + } + private function set_percentWidth(value:Null):Null { + if (_percentWidth == value) { + return value; + } + + _percentWidth = value; + + if (parentComponent != null) { + parentComponent.invalidateComponentLayout(); + } else { + Screen.instance.resizeRootComponents(); + } + return value; + } + + @:noCompletion private var _percentHeight:Null; + + /** + * When set, sets this component's height to be `percentHeight`% percent of it's parent's height. + */ + @:dox(group = "Size related properties and methods") + @clonable @bindable public var percentHeight(get, set):Null; + private function get_percentHeight():Null { + return _percentHeight; + } + private function set_percentHeight(value:Null):Null { + if (_percentHeight == value) { + return value; + } + _percentHeight = value; + + if (parentComponent != null) { + parentComponent.invalidateComponentLayout(); + } else { + Screen.instance.resizeRootComponents(); + } + return value; + } + + @:noCompletion private var _cachedPercentWidth:Null = null; + @:noCompletion private var _cachedPercentHeight:Null = null; + private function cachePercentSizes(clearExisting:Bool = true) { + if (_percentWidth != null) { + _cachedPercentWidth = _percentWidth; + if (clearExisting == true) { + _percentWidth = null; + } + } + if (_percentHeight != null) { + _cachedPercentHeight = _percentHeight; + if (clearExisting == true) { + _percentHeight = null; + } + } + } + + private function restorePercentSizes() { + if (_cachedPercentWidth != null) { + percentWidth = _cachedPercentWidth; + } + if (_cachedPercentHeight != null) { + percentHeight = _cachedPercentHeight; + } + } + + #if ((haxeui_openfl || haxeui_nme) && !haxeui_flixel) + + #if flash @:setter(x) #else override #end + public function set_x(value:Float): #if flash Void #else Float #end { + #if flash + super.x = value; + #else + super.set_x(value); + #end + left = value; + #if !flash return value; #end + } + + #if flash @:setter(y) #else override #end + public function set_y(value:Float): #if flash Void #else Float #end { + #if flash + super.y = value; + #else + super.set_y(value); + #end + top = value; + #if !flash return value; #end + } + + @:noCompletion private var _width:Null; + #if flash @:setter(width) #else override #end + private function set_width(value:Float): #if flash Void #else Float #end { + if (_width == value) { + return #if !flash value #end; + } + if (value == haxe.ui.util.MathUtil.MIN_INT) { + _width = null; + componentWidth = null; + } else { + _width = value; + componentWidth = value; + } + #if !flash return value; #end + } + + #if flash @:getter(width) #else override #end + private function get_width():Float { + var f:Float = componentWidth; + return f; + } + + @:noCompletion private var _height:Null; + #if flash @:setter(height) #else override #end + private function set_height(value:Float): #if flash Void #else Float #end { + if (_height == value) { + return #if !flash value #end; + } + if (value == haxe.ui.util.MathUtil.MIN_INT) { + _height = null; + componentHeight = null; + } else { + _height = value; + componentHeight = value; + } + #if !flash return value; #end + } + + #if flash @:getter(height) #else override #end + private function get_height():Float { + var f:Float = componentHeight; + return f; + } + + #elseif (haxeui_flixel) + + @:noCompletion private var _width:Null; + private override function set_width(value:Float):Float { + if (value == 0) { + return value; + } + if (_width == value) { + return value; + } + _width = value; + componentWidth = value; + return value; + } + + private override function get_width():Float { + var f:Float = componentWidth; + return f; + } + + @:noCompletion private var _height:Null; + private override function set_height(value:Float):Float { + if (value == 0) { + return value; + } + if (_height == value) { + return value; + } + _height = value; + componentHeight = value; + return value; + } + + private override function get_height() { + var f:Float = componentHeight; + return f; + } + + #else + + /** + This component's width. similar to `componentWidth` + **/ + @:dox(group = "Size related properties and methods") + @bindable public var width(get, set):Null; + @:noCompletion private var _width:Null; + private function set_width(value:Null):Null { + if (_width == value) { + return value; + } + _width = value; + componentWidth = value; + return value; + } + + private function get_width():Null { + var f:Float = componentWidth; + return f; + } + + /** + This component's height. similar to `componentHeight` + **/ + @:dox(group = "Size related properties and methods") + @bindable public var height(get, set):Null; + @:noCompletion private var _height:Null; + private function set_height(value:Null):Null { + if (_height == value) { + return value; + } + _height = value; + componentHeight = value; + return value; + } + + private function get_height():Null { + var f:Float = componentHeight; + return f; + } + + #end + + @:noCompletion private var _actualWidth:Null; + @:noCompletion private var _actualHeight:Null; + + @:noCompletion private var _hasScreen:Null = null; + + /** + * Whether this component, or one if it's parents, has a screen. + */ + public var hasScreen(get, null):Bool; + private function get_hasScreen():Bool { + var p = this; + while (p != null) { + if (p._hasScreen == false) { + return false; + } + p = p.parentComponent; + } + return true; + } + + /** + Whether or not a point is inside this components bounds + + *Note*: `left` and `top` must be stage (screen) co-ords + **/ + @:dox(group = "Size related properties and methods") + public function hitTest(left:Float, top:Float, allowZeroSized:Bool = false):Bool { // co-ords must be stage + + if (hasScreen == false) { + return false; + } + + left *= Toolkit.scale; + top *= Toolkit.scale; + + var b:Bool = false; + var sx:Float = screenLeft; + var sy:Float = screenTop; + + var cx:Float = 0; + if (componentWidth != null) { + cx = actualComponentWidth; + } + var cy:Float = 0; + if (componentHeight != null) { + cy = actualComponentHeight; + } + + if (allowZeroSized == true) { + /* + var c = cast(this, Component); + if (c.layout != null) { + var us = c.layout.usableSize; + if (us.width <= 0 || us.height <= 0) { + return true; + } + } + */ + if (this.width <= 0 || this.height <= 0) { + return true; + } + } + + if (left >= sx && left < sx + cx && top >= sy && top < sy + cy) { + b = true; + } + + return b; + } + + /** + Autosize this component based on its children + **/ + @:dox(group = "Size related properties and methods") + private function autoSize():Bool { + if (_componentReady == false || _layout == null) { + return false; + } + return _layout.autoSize(); + } + + //*********************************************************************************************************** + // Position related + //*********************************************************************************************************** + /** + Move this components left and top co-ord in one call + **/ + /** + * Moves this component to the position (`left`, `top`) in one function call. + * + * A more performant alternative to doing: + * + * component.x = value; + * component.y = anotherValue; + * + * @param left The x position of the top-left corner of this component + * @param top The y position of the top-left corner of this component + */ + @:dox(group = "Position related properties and methods") + public function moveComponent(left:Null, top:Null) { + var invalidate:Bool = false; + if (left != null && _left != left) { + _left = left; + invalidate = true; + } + if (top != null && _top != top) { + _top = top; + invalidate = true; + } + + if (invalidate == true && isComponentInvalid(InvalidationFlags.POSITION) == false) { + invalidateComponentPosition(); + } + } + + @:noCompletion private var _left:Null = 0; + /** + * The position of this component on the horizontal, x-axis. + * + * This position is relative to this component's parent. + */ + @:dox(group = "Position related properties and methods") + public var left(get, set):Null; + private function get_left():Null { + return _left; + } + private function set_left(value:Null):Null { + moveComponent(value, null); + return value; + } + + @:noCompletion private var _top:Null = 0; + /** + * The position of this component on the vertical, y-axis. + * + * This position is relative to this component's parent. + */ + @:dox(group = "Position related properties and methods") + public var top(get, set):Null; + private function get_top():Null { + return _top; + } + private function set_top(value:Null):Null { + moveComponent(null, value); + return value; + } + + /** + * The **on-screen** position of this component on the horizontal, x-axis. + */ + @:dox(group = "Position related properties and methods") + public var screenLeft(get, null):Float; + private function get_screenLeft():Float { + var c = this; + var xpos:Float = 0; + while (c != null) { + var l = c.left; + if (c.parentComponent != null) { + l *= Toolkit.scale; + } + xpos += l; + + if (c.componentClipRect != null) { + xpos -= c.componentClipRect.left * Toolkit.scaleX; + } + + c = c.parentComponent; + } + return xpos; + } + + /** + * The **on-screen** position of this component on the vertical, y-axis. + */ + @:dox(group = "Position related properties and methods") + public var screenTop(get, null):Float; + private function get_screenTop():Float { + var c = this; + var ypos:Float = 0; + while (c != null) { + var t = c.top; + if (c.parentComponent != null) { + t *= Toolkit.scale; + } + ypos += t; + + if (c.componentClipRect != null) { + ypos -= c.componentClipRect.top * Toolkit.scaleY; + } + + c = c.parentComponent; + } + return ypos; + } + + //*********************************************************************************************************** + // Clip rect + //*********************************************************************************************************** + @:noCompletion private var _componentClipRect:Rectangle = null; + + /** + * When set to a non-null value, restricts the component's "rendering zone" + * to only render inside the bounds of the given rectangle, effectively "clipping" the component. + */ + public var componentClipRect(get, set):Rectangle; + private function get_componentClipRect():Rectangle { + if (style != null && style.clip != null && style.clip == true) { + return new Rectangle(0, 0, componentWidth, componentHeight); + } + return _componentClipRect; + } + private function set_componentClipRect(value:Rectangle):Rectangle { + _componentClipRect = value; + invalidateComponentDisplay(); + return value; + } + + /** + * Whether this component has a non-null clipping rectangle or not. + */ + public var isComponentClipped(get, null):Bool; + private function get_isComponentClipped():Bool { + return (componentClipRect != null); + } + + /** + * `true` if this component's area intersects with the screen, `false` otherwise. + * + * clipRect is not taken into consideration - that means, if a clipRect turns a component from being + * visible on screen to being invisible on screen, `isComponentOffScreen` should still be `false`. + * + */ + public var isComponentOffscreen(get, null):Bool; + private function get_isComponentOffscreen():Bool { + if (this.width == 0 && this.height == 0) { + return false; + } + var x:Float = screenLeft; + var y:Float = screenTop; + var w:Float = this.width; + var h:Float = this.height; + + var thisRect = new Rectangle(x, y, w, h); + var screenRect = new Rectangle(0, 0, Screen.instance.width, Screen.instance.height); + return !screenRect.intersects(thisRect); + } + + //*********************************************************************************************************** + // Style related + //*********************************************************************************************************** + @:noCompletion private var _style:Style = null; + @:dox(group = "Style related properties and methods") + public var style(get, set):Style; + private function get_style():Style { + return _style; + } + + private function set_style(value):Style { + _style = value; + return value; + } + + //*********************************************************************************************************** + // Validation + //*********************************************************************************************************** + @:noCompletion private var _invalidationFlags:Map = new Map(); + @:noCompletion private var _delayedInvalidationFlags:Map = new Map(); + @:noCompletion private var _isAllInvalid:Bool = false; + @:noCompletion private var _isValidating:Bool = false; + @:noCompletion private var _isInitialized:Bool = false; + @:noCompletion private var _isDisposed:Bool = false; + @:noCompletion private var _invalidateCount:Int = 0; + + @:noCompletion private var _depth:Int = -1; + @:dox(group = "Internal") + public var depth(get, set):Int; + private function get_depth():Int { + return _depth; + } + private function set_depth(value:Int):Int { + if (_depth == value) { + return value; + } + + _depth = value; + + return value; + } + + /** + Check if the component is invalidated with some `flag`. + **/ + @:dox(group = "Invalidation related properties and methods") + public function isComponentInvalid(flag:String = InvalidationFlags.ALL):Bool { + if (_isAllInvalid == true) { + return true; + } + + if (flag == InvalidationFlags.ALL) { + for (value in _invalidationFlags) { + return true; + } + + return false; + } + + return _invalidationFlags.exists(flag); + } + + /** + Invalidate this components with the `InvalidationFlags` indicated. If it hasn't parameter then the component will be invalidated completely. + **/ + @:dox(group = "Invalidation related properties and methods") + public function invalidateComponent(flag:String = InvalidationFlags.ALL, recursive:Bool = false) { + if (_componentReady == false) { + return; //it should be added into the queue later + } + + var isAlreadyInvalid:Bool = isComponentInvalid(flag); + var isAlreadyDelayedInvalid:Bool = false; + if (_isValidating == true) { + for (value in _delayedInvalidationFlags) { + isAlreadyDelayedInvalid = true; + break; + } + } + + if (flag == InvalidationFlags.ALL) { + if (_isValidating == true) { + _delayedInvalidationFlags.set(InvalidationFlags.ALL, true); + } else { + _isAllInvalid = true; + } + } else { + if (_isValidating == true) { + _delayedInvalidationFlags.set(flag, true); + } else if (flag != InvalidationFlags.ALL && !_invalidationFlags.exists(flag)) { + _invalidationFlags.set(flag, true); + } + } + + if (_isValidating == true) { + //it is already in queue + if (isAlreadyDelayedInvalid == true) { + return; + } + + _invalidateCount++; + + //we track the invalidate count to check if we are in an infinite loop or serious bug because it affects performance + if (this._invalidateCount >= 10) { + throw 'The validation queue returned too many times during validation. This may be an infinite loop. Try to avoid doing anything that calls invalidate() during validation.'; + } + + ValidationManager.instance.add(cast(this, Component)); // TODO: avoid cast + return; + } else if (isAlreadyInvalid == true) { + if (recursive == true) { + for (child in childComponents) { + child.invalidateComponent(flag, recursive); + } + } + return; + } + + _invalidateCount = 0; + ValidationManager.instance.add(cast(this, Component)); // TODO: avoid cast + + if (recursive == true) { + for (child in childComponents) { + child.invalidateComponent(flag, recursive); + } + } + } + + /** + Invalidate the data of this component + **/ + @:dox(group = "Invalidation related properties and methods") + public inline function invalidateComponentData(recursive:Bool = false) { + invalidateComponent(InvalidationFlags.DATA, recursive); + } + + /** + Invalidate this components layout, may result in multiple calls to `invalidateDisplay` and `invalidateLayout` of its children + **/ + @:dox(group = "Invalidation related properties and methods") + public inline function invalidateComponentLayout(recursive:Bool = false) { + if (_layout == null || _layoutLocked == true) { + return; + } + invalidateComponent(InvalidationFlags.LAYOUT, recursive); + } + + /** + Invalidate the position of this component + **/ + @:dox(group = "Invalidation related properties and methods") + public inline function invalidateComponentPosition(recursive:Bool = false) { + invalidateComponent(InvalidationFlags.POSITION, recursive); + } + + /** + Invalidate the visible aspect of this component + **/ + @:dox(group = "Invalidation related properties and methods") + public inline function invalidateComponentDisplay(recursive:Bool = false) { + invalidateComponent(InvalidationFlags.DISPLAY, recursive); + } + + /** + Invalidate and recalculate this components style, may result in a call to `invalidateDisplay` + **/ + @:dox(group = "Invalidation related properties and methods") + public inline function invalidateComponentStyle(force:Bool = false, recursive:Bool = false) { + invalidateComponent(InvalidationFlags.STYLE, recursive); + if (force == true) { + _style = null; + } + } + + /** + This method validates the tasks pending in the component. + **/ + @:dox(group = "Invalidation related properties and methods") + public function validateComponent(nextFrame:Bool = true) { + if (_componentReady == false || + _isDisposed == true || //we don't want to validate disposed components, but they may have been left in the queue. + _isValidating == true || //we were already validating, the existing validation will continue. + isComponentInvalid() == false) { //if none is invalid, exit. + return; + } + + var isInitialized = _isInitialized; + if (isInitialized == false) { + initializeComponent(); + } + + _isValidating = true; + + validateComponentInternal(nextFrame); + validateInitialSize(isInitialized); + + #if (haxe_ver < 4) + _invalidationFlags = new Map(); + #else + _invalidationFlags.clear(); + #end + + _isAllInvalid = false; + + for (flag in _delayedInvalidationFlags.keys()) { + if (flag == InvalidationFlags.ALL) { + _isAllInvalid = true; + } else { + _invalidationFlags.set(flag, true); + } + } + #if (haxe_ver < 4) + _delayedInvalidationFlags = new Map(); + #else + _delayedInvalidationFlags.clear(); + #end + + _isValidating = false; + } + + /** + Validate this component and its children on demand. + **/ + @:dox(group = "Invalidation related properties and methods") + public function validateNow() { + for (child in childComponents) { + child.validateNow(); + } + invalidateComponent(); + syncComponentValidation(false); + } + + /** + Validate this component and its children on demand. + **/ + @:dox(group = "Invalidation related properties and methods") + public function syncComponentValidation(nextFrame:Bool = true) { + var count:Int = 0; + while (isComponentInvalid()) { + validateComponent(nextFrame); + + for (child in childComponents) { + child.syncComponentValidation(nextFrame); + } + + if (++count >= 10) { + if (this._isDisposed) { + #if debug + trace('There was a problem validating this component as it has already been destroyed (${Type.getClassName(Type.getClass(this))}#${this.id})'); + #end + throw 'There was a problem validating this component as it has already been destroyed (${Type.getClassName(Type.getClass(this))}#${this.id})'; + } else { + #if debug + trace('The syncValidation returned too many times during validation. This may be an infinite loop. Try to avoid doing anything that calls invalidate() during validation (${Type.getClassName(Type.getClass(this))}#${this.id}).'); + #end + throw 'The syncValidation returned too many times during validation. This may be an infinite loop. Try to avoid doing anything that calls invalidate() during validation (${Type.getClassName(Type.getClass(this))}#${this.id}).'; + } + } + } + } + + private function validateComponentInternal(nextFrame:Bool = true) { + var dataInvalid = isComponentInvalid(InvalidationFlags.DATA); + var styleInvalid = isComponentInvalid(InvalidationFlags.STYLE); + var textDisplayInvalid = isComponentInvalid(InvalidationFlags.TEXT_DISPLAY) && hasTextDisplay(); + var textInputInvalid = isComponentInvalid(InvalidationFlags.TEXT_INPUT) && hasTextInput(); + var imageDisplayInvalid = isComponentInvalid(InvalidationFlags.IMAGE_DISPLAY) && hasImageDisplay(); + var positionInvalid = isComponentInvalid(InvalidationFlags.POSITION); + var displayInvalid = isComponentInvalid(InvalidationFlags.DISPLAY); + var layoutInvalid = isComponentInvalid(InvalidationFlags.LAYOUT) && _layoutLocked == false; + + if (dataInvalid) { + validateComponentData(); + } + + if (styleInvalid) { + validateComponentStyle(); + } + + if (textDisplayInvalid) { + getTextDisplay().validateComponent(); + } + + if (textInputInvalid) { + getTextInput().validateComponent(); + } + + if (imageDisplayInvalid) { + getImageDisplay().validateComponent(); + } + + if (positionInvalid) { + validateComponentPosition(); + } + + if (layoutInvalid) { + displayInvalid = validateComponentLayout() || displayInvalid; + } + + if (displayInvalid || styleInvalid) { + ValidationManager.instance.addDisplay(cast(this, Component), nextFrame); //Update the display from all objects at the same time. Avoids UI flashes. + } + } + + private function initializeComponent() { + + } + + private function validateInitialSize(isInitialized:Bool) { + + } + + private function validateComponentData() { + } + + private function validateComponentLayout():Bool { return false; } - private function handleFrameworkProperty(id:String, value:Any) { + private function validateComponentStyle() { + + } + + private function validateComponentPosition() { } //*********************************************************************************************************** - // Display tree + // Backend //*********************************************************************************************************** + @:dox(group = "Backend") + private function handleCreate(native:Bool) { + } - private function handleSetComponentIndex(child:Component, index:Int) { + @:dox(group = "Backend") + private function handlePosition(left:Null, top:Null, style:Style) { + } + @:dox(group = "Backend") + public function handlePreReposition() { + } + + @:dox(group = "Backend") + public function handlePostReposition() { + } + + @:dox(group = "Backend") + private function handleSize(width:Null, height:Null, style:Style) { + } + + @:dox(group = "Backend") + private function handleReady() { } + @:dox(group = "Backend") + private function handleClipRect(value:Rectangle) { + } + + @:dox(group = "Backend") + private function handleVisibility(show:Bool) { + } + + @:dox(group = "Backend") + private function handleDisabled(show:Bool) { + } + + @:dox(group = "Backend") + private function handleSetComponentIndex(child:Component, index:Int) { + } + + @:dox(group = "Backend") private function handleAddComponent(child:Component):Component { return child; } + @:dox(group = "Backend") private function handleAddComponentAt(child:Component, index:Int):Component { return child; } + @:dox(group = "Backend") private function handleRemoveComponent(child:Component, dispose:Bool = true):Component { return child; } + @:dox(group = "Backend") private function handleRemoveComponentAt(index:Int, dispose:Bool = true):Component { return null; } + @:dox(group = "Backend") private function applyStyle(style:Style) { } - //*********************************************************************************************************** - // Events - //*********************************************************************************************************** - private override function mapEvent(type:String, listener:UIEvent->Void) { + @:dox(group = "Backend") + private function mapEvent(type:String, listener:UIEvent->Void) { + } + + @:dox(group = "Backend") + private function unmapEvent(type:String, listener:UIEvent->Void) { } - private override function unmapEvent(type:String, listener:UIEvent->Void) { + private function getComponentOffset():Point { + return new Point(0, 0); + } + + private var isNativeScroller(get, null):Bool; + private function get_isNativeScroller():Bool { + return false; + } + + private var isScroller(get, null):Bool; + private function get_isScroller():Bool { + return false; + } + + private function handleFrameworkProperty(id:String, value:Any) { } //*********************************************************************************************************** - // Text related + // Backend - Text related //*********************************************************************************************************** @:noCompletion private var _textDisplay:TextDisplay; @:dox(group = "Backend") @@ -111,12 +1606,12 @@ class ComponentBase extends ComponentBounds { } @:dox(group = "Backend") - public override function getTextDisplay():TextDisplay { + public function getTextDisplay():TextDisplay { return createTextDisplay(); } @:dox(group = "Backend") - public override function hasTextDisplay():Bool { + public function hasTextDisplay():Bool { return (_textDisplay != null); } @@ -134,17 +1629,17 @@ class ComponentBase extends ComponentBounds { } @:dox(group = "Backend") - public override function getTextInput():TextInput { + public function getTextInput():TextInput { return createTextInput(); } @:dox(group = "Backend") - public override function hasTextInput():Bool { + public function hasTextInput():Bool { return (_textInput != null); } //*********************************************************************************************************** - // Image related + // Backend - Image related //*********************************************************************************************************** @:noCompletion private var _imageDisplay:ImageDisplay; @:dox(group = "Backend") @@ -157,12 +1652,12 @@ class ComponentBase extends ComponentBounds { } @:dox(group = "Backend") - public override function getImageDisplay():ImageDisplay { + public function getImageDisplay():ImageDisplay { return createImageDisplay(); } @:dox(group = "Backend") - public override function hasImageDisplay():Bool { + public function hasImageDisplay():Bool { return (_imageDisplay != null); } @@ -174,18 +1669,6 @@ class ComponentBase extends ComponentBounds { } } - //*********************************************************************************************************** - // Misc - //*********************************************************************************************************** - - @:dox(group = "Backend") - public function handlePreReposition() { - } - - @:dox(group = "Backend") - public function handlePostReposition() { - } - //*********************************************************************************************************** // Properties //*********************************************************************************************************** @@ -288,4 +1771,110 @@ class ComponentBase extends ComponentBounds { return _nativeClassName; } -} \ No newline at end of file +} + +//*********************************************************************************************************** +// Default behaviours +//*********************************************************************************************************** +@:dox(hide) @:noCompletion +@:access(haxe.ui.core.Component) +class ComponentTextBehaviour extends DefaultBehaviour { + public override function set(value:Variant) { + if (value == _value) { + return; + } + + _value = value; + + super.set(value); + } +} + +@:dox(hide) @:noCompletion +@:access(haxe.ui.core.Component) +class ComponentDisabledBehaviour extends DefaultBehaviour { + public function new(component:Component) { + super(component); + _value = false; + } + + public override function set(value:Variant) { + if (value == _value) { + return; + } + _value = value; + if (value != null && value.isNull == false) { + _component.disableInteractivity(value, true, true); + } + } + + public override function get():Variant { + return _component.hasClass(":disabled"); + } +} + +@:dox(hide) @:noCompletion +@:access(haxe.ui.core.Component) +class ComponentValueBehaviour extends ValueBehaviour { + public override function set(value:Variant) { + if (value == _value) { + return; + } + + _value = value; + _component.text = value; + } + + public override function get():Variant { + return _value; + } + + public override function getDynamic():Dynamic { + return Variant.toDynamic(_value); + } +} + +@:dox(hide) @:noCompletion +@:access(haxe.ui.core.Component) +class ComponentToolTipBehaviour extends DataBehaviour { + public override function validateData() { + if (_value == null || _value.isNull) { + ToolTipManager.instance.unregisterTooltip(_component); + } else { + ToolTipManager.instance.registerTooltip(_component, { + tipData: Variant.toDynamic(_value), + renderer: cast _component.tooltipRenderer + }); + } + } + + public override function setDynamic(value:Dynamic) { + ToolTipManager.instance.unregisterTooltip(_component); + if (value != null) { + ToolTipManager.instance.registerTooltip(_component, { + tipData: value, + renderer: cast _component.tooltipRenderer + }); + } + } + + public override function getDynamic():Dynamic { + var options = ToolTipManager.instance.getTooltipOptions(_component); + if (options == null) { + return null; + } + return options.tipData; + } +} + +@:dox(hide) @:noCompletion +@:access(haxe.ui.core.Component) +class ComponentToolTipRendererBehaviour extends DataBehaviour { + public override function validateData() { + if (_value == null || _value.isNull) { + ToolTipManager.instance.updateTooltipRenderer(_component, null); + } else { + ToolTipManager.instance.updateTooltipRenderer(_component, cast _value.toComponent()); + } + } +} diff --git a/haxe/ui/core/Component.hx b/haxe/ui/core/Component.hx index 78dd8f6b8..c17a380eb 100644 --- a/haxe/ui/core/Component.hx +++ b/haxe/ui/core/Component.hx @@ -37,8 +37,6 @@ import Std.is as isOfType; * Base class of all HaxeUI controls */ @:allow(haxe.ui.backend.ComponentImpl) -@:build(haxe.ui.macros.Macros.build()) -@:autoBuild(haxe.ui.macros.Macros.build()) class Component extends ComponentImpl implements IValidating { /** diff --git a/haxe/ui/core/ComponentBounds.hx b/haxe/ui/core/ComponentBounds.hx deleted file mode 100644 index 594e8bb05..000000000 --- a/haxe/ui/core/ComponentBounds.hx +++ /dev/null @@ -1,611 +0,0 @@ -package haxe.ui.core; - -import haxe.ui.geom.Rectangle; -import haxe.ui.validation.InvalidationFlags; - -class ComponentBounds extends ComponentLayout { - - //*********************************************************************************************************** - // Size related - //*********************************************************************************************************** - - /** - * When enabled, this component will automatically resize itself based on it's children's calculated width. - * - * For example, if this component's padding is `5`, and it has one child, `150` pixels wide, - * and `autoWidth` is set to `true`, this component's width should be `160` - */ - @:dox(group = "Size related properties and methods") - public var autoWidth(get, null):Bool; - private function get_autoWidth():Bool { - if (_percentWidth != null || _width != null) { - return false; - } - - if (style == null) { - return true; - } - - if (style.autoWidth == null) { - return false; - } - return style.autoWidth; - } - - /** - * When enabled, this component will automatically resize itself based on it's children's calculated height. - * - * For example, if this component's padding is `5`, and it has one child, `200` pixels tall, - * and `autoHeight` is set to `true`, this component's height should be `210` - */ - @:dox(group = "Size related properties and methods") - public var autoHeight(get, null):Bool; - private function get_autoHeight():Bool { - if (_percentHeight != null || _height != null || style == null) { - return false; - } - if (style.autoHeight == null) { - return false; - } - return style.autoHeight; - } - - /** - * Resizes a component to be `w` pixels wide and `h` pixels tall. - * - * Useful if you want to resize the component in both the X & Y axis, - * and don't want to call the resizing logic twice. - * @param w The component's new width. - * @param h The component's new height. - */ - @:dox(group = "Size related properties and methods") - public function resizeComponent(w:Null, h:Null) { - var invalidate:Bool = false; - - if (w != null && _componentWidth != w) { - _componentWidth = w; - invalidate = true; - } - - if (h != null && _componentHeight != h) { - _componentHeight = h; - invalidate = true; - } - - if (invalidate == true && isComponentInvalid(InvalidationFlags.LAYOUT) == false) { - invalidateComponentLayout(); - } - } - - /** - * The component's true width on screen. - * - * May differ from `componentWidth` if `Toolkit.scaleX != 1` - */ - public var actualComponentWidth(get, null):Float; - private function get_actualComponentWidth():Float { - return componentWidth * Toolkit.scaleX; - } - - /** - * The component's true height on screen. - * - * May differ from `componentHeight` if `Toolkit.scaleY != 1` - */ - public var actualComponentHeight(get, null):Float; - private function get_actualComponentHeight():Float { - return componentHeight * Toolkit.scaleY; - } - - @:noCompletion private var _componentWidth:Null; - @:allow(haxe.ui.layouts.Layout) - @:allow(haxe.ui.core.Screen) - - /** - * This component's calculated width. - */ - @:dox(group = "Size related properties and methods") - @:clonable private var componentWidth(get, set):Null; - private function get_componentWidth():Null { - if (_componentWidth == null) { - return 0; - } - return _componentWidth; - } - private function set_componentWidth(value:Null):Null { - resizeComponent(value, null); - return value; - } - - @:noCompletion private var _componentHeight:Null; - @:allow(haxe.ui.layouts.Layout) - @:allow(haxe.ui.core.Screen) - - /** - * This component's calculated height. - */ - @:dox(group = "Size related properties and methods") - @:clonable private var componentHeight(get, set):Null; - private function get_componentHeight():Null { - if (_componentHeight == null) { - return 0; - } - return _componentHeight; - } - private function set_componentHeight(value:Null):Null { - resizeComponent(null, value); - return value; - } - - @:noCompletion private var _percentWidth:Null; - - /** - * When set, sets this component's width to be `percentWidth`% percent of it's parent's width. - */ - @:dox(group = "Size related properties and methods") - @clonable @bindable public var percentWidth(get, set):Null; - private function get_percentWidth():Null { - return _percentWidth; - } - private function set_percentWidth(value:Null):Null { - if (_percentWidth == value) { - return value; - } - - _percentWidth = value; - - if (parentComponent != null) { - parentComponent.invalidateComponentLayout(); - } else { - Screen.instance.resizeRootComponents(); - } - return value; - } - - @:noCompletion private var _percentHeight:Null; - - /** - * When set, sets this component's height to be `percentHeight`% percent of it's parent's height. - */ - @:dox(group = "Size related properties and methods") - @clonable @bindable public var percentHeight(get, set):Null; - private function get_percentHeight():Null { - return _percentHeight; - } - private function set_percentHeight(value:Null):Null { - if (_percentHeight == value) { - return value; - } - _percentHeight = value; - - if (parentComponent != null) { - parentComponent.invalidateComponentLayout(); - } else { - Screen.instance.resizeRootComponents(); - } - return value; - } - - @:noCompletion private var _cachedPercentWidth:Null = null; - @:noCompletion private var _cachedPercentHeight:Null = null; - private function cachePercentSizes(clearExisting:Bool = true) { - if (_percentWidth != null) { - _cachedPercentWidth = _percentWidth; - if (clearExisting == true) { - _percentWidth = null; - } - } - if (_percentHeight != null) { - _cachedPercentHeight = _percentHeight; - if (clearExisting == true) { - _percentHeight = null; - } - } - } - - private function restorePercentSizes() { - if (_cachedPercentWidth != null) { - percentWidth = _cachedPercentWidth; - } - if (_cachedPercentHeight != null) { - percentHeight = _cachedPercentHeight; - } - } - - #if ((haxeui_openfl || haxeui_nme) && !haxeui_flixel) - - #if flash @:setter(x) #else override #end - public function set_x(value:Float): #if flash Void #else Float #end { - #if flash - super.x = value; - #else - super.set_x(value); - #end - left = value; - #if !flash return value; #end - } - - #if flash @:setter(y) #else override #end - public function set_y(value:Float): #if flash Void #else Float #end { - #if flash - super.y = value; - #else - super.set_y(value); - #end - top = value; - #if !flash return value; #end - } - - @:noCompletion private var _width:Null; - #if flash @:setter(width) #else override #end - private function set_width(value:Float): #if flash Void #else Float #end { - if (_width == value) { - return #if !flash value #end; - } - if (value == haxe.ui.util.MathUtil.MIN_INT) { - _width = null; - componentWidth = null; - } else { - _width = value; - componentWidth = value; - } - #if !flash return value; #end - } - - #if flash @:getter(width) #else override #end - private function get_width():Float { - var f:Float = componentWidth; - return f; - } - - @:noCompletion private var _height:Null; - #if flash @:setter(height) #else override #end - private function set_height(value:Float): #if flash Void #else Float #end { - if (_height == value) { - return #if !flash value #end; - } - if (value == haxe.ui.util.MathUtil.MIN_INT) { - _height = null; - componentHeight = null; - } else { - _height = value; - componentHeight = value; - } - #if !flash return value; #end - } - - #if flash @:getter(height) #else override #end - private function get_height():Float { - var f:Float = componentHeight; - return f; - } - - #elseif (haxeui_flixel) - - @:noCompletion private var _width:Null; - private override function set_width(value:Float):Float { - if (value == 0) { - return value; - } - if (_width == value) { - return value; - } - _width = value; - componentWidth = value; - return value; - } - - private override function get_width():Float { - var f:Float = componentWidth; - return f; - } - - @:noCompletion private var _height:Null; - private override function set_height(value:Float):Float { - if (value == 0) { - return value; - } - if (_height == value) { - return value; - } - _height = value; - componentHeight = value; - return value; - } - - private override function get_height() { - var f:Float = componentHeight; - return f; - } - - #else - - /** - This component's width. similar to `componentWidth` - **/ - @:dox(group = "Size related properties and methods") - @bindable public var width(get, set):Null; - @:noCompletion private var _width:Null; - private function set_width(value:Null):Null { - if (_width == value) { - return value; - } - _width = value; - componentWidth = value; - return value; - } - - private function get_width():Null { - var f:Float = componentWidth; - return f; - } - - /** - This component's height. similar to `componentHeight` - **/ - @:dox(group = "Size related properties and methods") - @bindable public var height(get, set):Null; - @:noCompletion private var _height:Null; - private function set_height(value:Null):Null { - if (_height == value) { - return value; - } - _height = value; - componentHeight = value; - return value; - } - - private function get_height():Null { - var f:Float = componentHeight; - return f; - } - - #end - - @:noCompletion private var _actualWidth:Null; - @:noCompletion private var _actualHeight:Null; - - @:noCompletion private var _hasScreen:Null = null; - - /** - * Whether this component, or one if it's parents, has a screen. - */ - public var hasScreen(get, null):Bool; - private function get_hasScreen():Bool { - var p = this; - while (p != null) { - if (p._hasScreen == false) { - return false; - } - p = p.parentComponent; - } - return true; - } - - /** - Whether or not a point is inside this components bounds - - *Note*: `left` and `top` must be stage (screen) co-ords - **/ - @:dox(group = "Size related properties and methods") - public function hitTest(left:Float, top:Float, allowZeroSized:Bool = false):Bool { // co-ords must be stage - - if (hasScreen == false) { - return false; - } - - left *= Toolkit.scale; - top *= Toolkit.scale; - - var b:Bool = false; - var sx:Float = screenLeft; - var sy:Float = screenTop; - - var cx:Float = 0; - if (componentWidth != null) { - cx = actualComponentWidth; - } - var cy:Float = 0; - if (componentHeight != null) { - cy = actualComponentHeight; - } - - if (allowZeroSized == true) { - /* - var c = cast(this, Component); - if (c.layout != null) { - var us = c.layout.usableSize; - if (us.width <= 0 || us.height <= 0) { - return true; - } - } - */ - if (this.width <= 0 || this.height <= 0) { - return true; - } - } - - if (left >= sx && left < sx + cx && top >= sy && top < sy + cy) { - b = true; - } - - return b; - } - - /** - Autosize this component based on its children - **/ - @:dox(group = "Size related properties and methods") - private function autoSize():Bool { - if (_componentReady == false || _layout == null) { - return false; - } - return _layout.autoSize(); - } - - //*********************************************************************************************************** - // Position related - //*********************************************************************************************************** - /** - Move this components left and top co-ord in one call - **/ - /** - * Moves this component to the position (`left`, `top`) in one function call. - * - * A more performant alternative to doing: - * - * component.x = value; - * component.y = anotherValue; - * - * @param left The x position of the top-left corner of this component - * @param top The y position of the top-left corner of this component - */ - @:dox(group = "Position related properties and methods") - public function moveComponent(left:Null, top:Null) { - var invalidate:Bool = false; - if (left != null && _left != left) { - _left = left; - invalidate = true; - } - if (top != null && _top != top) { - _top = top; - invalidate = true; - } - - if (invalidate == true && isComponentInvalid(InvalidationFlags.POSITION) == false) { - invalidateComponentPosition(); - } - } - - @:noCompletion private var _left:Null = 0; - /** - * The position of this component on the horizontal, x-axis. - * - * This position is relative to this component's parent. - */ - @:dox(group = "Position related properties and methods") - public var left(get, set):Null; - private function get_left():Null { - return _left; - } - private function set_left(value:Null):Null { - moveComponent(value, null); - return value; - } - - @:noCompletion private var _top:Null = 0; - /** - * The position of this component on the vertical, y-axis. - * - * This position is relative to this component's parent. - */ - @:dox(group = "Position related properties and methods") - public var top(get, set):Null; - private function get_top():Null { - return _top; - } - private function set_top(value:Null):Null { - moveComponent(null, value); - return value; - } - - /** - * The **on-screen** position of this component on the horizontal, x-axis. - */ - @:dox(group = "Position related properties and methods") - public var screenLeft(get, null):Float; - private function get_screenLeft():Float { - var c = this; - var xpos:Float = 0; - while (c != null) { - var l = c.left; - if (c.parentComponent != null) { - l *= Toolkit.scale; - } - xpos += l; - - if (c.componentClipRect != null) { - xpos -= c.componentClipRect.left * Toolkit.scaleX; - } - - c = c.parentComponent; - } - return xpos; - } - - /** - * The **on-screen** position of this component on the vertical, y-axis. - */ - @:dox(group = "Position related properties and methods") - public var screenTop(get, null):Float; - private function get_screenTop():Float { - var c = this; - var ypos:Float = 0; - while (c != null) { - var t = c.top; - if (c.parentComponent != null) { - t *= Toolkit.scale; - } - ypos += t; - - if (c.componentClipRect != null) { - ypos -= c.componentClipRect.top * Toolkit.scaleY; - } - - c = c.parentComponent; - } - return ypos; - } - - //*********************************************************************************************************** - // Clip rect - //*********************************************************************************************************** - @:noCompletion private var _componentClipRect:Rectangle = null; - - /** - * When set to a non-null value, restricts the component's "rendering zone" - * to only render inside the bounds of the given rectangle, effectively "clipping" the component. - */ - public var componentClipRect(get, set):Rectangle; - private function get_componentClipRect():Rectangle { - if (style != null && style.clip != null && style.clip == true) { - return new Rectangle(0, 0, componentWidth, componentHeight); - } - return _componentClipRect; - } - private function set_componentClipRect(value:Rectangle):Rectangle { - _componentClipRect = value; - invalidateComponentDisplay(); - return value; - } - - /** - * Whether this component has a non-null clipping rectangle or not. - */ - public var isComponentClipped(get, null):Bool; - private function get_isComponentClipped():Bool { - return (componentClipRect != null); - } - - /** - * `true` if this component's area intersects with the screen, `false` otherwise. - * - * clipRect is not taken into consideration - that means, if a clipRect turns a component from being - * visible on screen to being invisible on screen, `isComponentOffScreen` should still be `false`. - * - */ - public var isComponentOffscreen(get, null):Bool; - private function get_isComponentOffscreen():Bool { - if (this.width == 0 && this.height == 0) { - return false; - } - var x:Float = screenLeft; - var y:Float = screenTop; - var w:Float = this.width; - var h:Float = this.height; - - var thisRect = new Rectangle(x, y, w, h); - var screenRect = new Rectangle(0, 0, Screen.instance.width, Screen.instance.height); - return !screenRect.intersects(thisRect); - } -} \ No newline at end of file diff --git a/haxe/ui/core/ComponentCommon.hx b/haxe/ui/core/ComponentCommon.hx deleted file mode 100644 index f2f7fedae..000000000 --- a/haxe/ui/core/ComponentCommon.hx +++ /dev/null @@ -1,45 +0,0 @@ -package haxe.ui.core; - -import haxe.ui.backend.ComponentSurface; - -class ComponentCommon extends ComponentSurface { - private function handleDisabled(show:Bool) { - - } - - //*********************************************************************************************************** - // Text related - //*********************************************************************************************************** - @:dox(group = "Backend") - public function getTextDisplay():TextDisplay { - return null; - } - - @:dox(group = "Backend") - public function hasTextDisplay():Bool { - return false; - } - - @:dox(group = "Backend") - public function getTextInput():TextInput { - return null; - } - - @:dox(group = "Backend") - public function hasTextInput():Bool { - return false; - } - - //*********************************************************************************************************** - // Image related - //*********************************************************************************************************** - @:dox(group = "Backend") - public function getImageDisplay():ImageDisplay { - return null; - } - - @:dox(group = "Backend") - public function hasImageDisplay():Bool { - return false; - } -} \ No newline at end of file diff --git a/haxe/ui/core/ComponentContainer.hx b/haxe/ui/core/ComponentContainer.hx deleted file mode 100644 index 12b931397..000000000 --- a/haxe/ui/core/ComponentContainer.hx +++ /dev/null @@ -1,371 +0,0 @@ -package haxe.ui.core; - -import haxe.ui.behaviours.Behaviours; -import haxe.ui.behaviours.DataBehaviour; -import haxe.ui.behaviours.DefaultBehaviour; -import haxe.ui.behaviours.ValueBehaviour; -import haxe.ui.events.UIEvent; -import haxe.ui.layouts.Layout; -import haxe.ui.styles.Style; -import haxe.ui.tooltips.ToolTipManager; -import haxe.ui.util.Variant; - -@:build(haxe.ui.macros.Macros.buildBehaviours()) -@:autoBuild(haxe.ui.macros.Macros.buildBehaviours()) -class ComponentContainer extends ComponentCommon implements IClonable { - - /** - * Whether to disable interactivity for this component or not. - * - * The user can't interact with disabled components, and they don't - * send state change events when the user tries to interact with them. - */ - @:clonable @:behaviour(ComponentDisabledBehaviour) public var disabled:Bool; - - /** - * A tooltip to display near the cursor when hovering on top of this component for a while. - * - * @see http://haxeui.org/explorer/#miscellaneous/tooltips - */ - @:clonable @:behaviour(ComponentToolTipBehaviour, null) public var tooltip:Dynamic; - - /** - * Some sort of a "default" tooltip layout used when assigning tooltips. - * Might be useful when you want to use complex tooltips. - * - * @see http://haxeui.org/explorer/#miscellaneous/tooltips - */ - @:clonable @:behaviour(ComponentToolTipRendererBehaviour, null) public var tooltipRenderer:Component; - - private var behaviours:Behaviours; - - /** - The parent component of this component instance. - - Returns `null` if this component hasn't been added yet, or just doesn't have a parent. - **/ - @:dox(group = "Display tree related properties and methods") - public var parentComponent:Component = null; - - /** - * Creates a new `ComponentContainer`. - */ - public function new() { - super(); - behaviours = new Behaviours(cast(this, Component)); - } - - public function dispatch(event:UIEvent) { - } - - @:noCompletion private var _componentReady:Bool = false; - /** - Whether the framework considers this component ready or not. - **/ - public var isReady(get, null):Bool; - private function get_isReady():Bool { - return _componentReady; - } - - @:noCompletion private var _children:Array; - /** - * An array of this component's children. - * - * Note: If this component has no children, and empty array is returned. - **/ - @:dox(group = "Display tree related properties and methods") - public var childComponents(get, null):Array; - private inline function get_childComponents():Array { - if (_children == null) { - return []; - } - return _children; - } - - private function registerBehaviours() {} - - /** - * Adds a component to the end of this component's display list. - * - * If this component already has children, the given component is added in front of the other children. - * - * @param child The child component to add to this component. - * @return The added component. - */ - public function addComponent(child:Component):Component { - return null; - } - - /** - * Inserts a child component after `index` children, effectively adding it "in front" of `index` children, and "behind" the rest. - * - * If `index` is below every other child's index, the added component will render behind this component's children. - * If `index` is above every other child's index, the added component will render in front of this component's children. - * - * For example, inserting a child into a `VBox` at `index = 0` will place it at the top, "behind" all other children - * - * @param child The child component to add to this component. - * @param index The index at which the child component should be added. - * @return The added component. - */ - public function addComponentAt(child:Component, index:Int):Component { - return null; - } - - /** - * Removes a child component from this component's display list, and returns it. - * - * @param child The child component to remove from this component. - * @param dispose Decides whether or not the child component should be destroyed too. - * @param invalidate If `true`, the child component updates itself after the removal. - * @return The removed child component - */ - public function removeComponent(child:Component, dispose:Bool = true, invalidate:Bool = true):Component { - return null; - } - - /** - * Removes the child component at index `index` from this component's display list, and returns it. - * - * @param index The index of the child component to remove from this component. - * @param dispose Decides whether or not the child component should be destroyed too. - * @param invalidate If `true`, the child component updates itself after the removal. - * @return The removed child component - */ - public function removeComponentAt(index:Int, dispose:Bool = true, invalidate:Bool = true):Component { - return null; - } - - /** - * Sets this component's z-index to `0`, - * effectively putting it behind every single one of the parent's children. - */ - public function moveComponentToBack() { - if (parentComponent == null || parentComponent.numComponents <= 1) { - return; - } - - parentComponent.setComponentIndex(cast this, 0); - } - - /** - * Moves this component behind the child component behind it. - */ - public function moveComponentBackward() { - if (parentComponent == null || parentComponent.numComponents <= 1) { - return; - } - - var index = parentComponent.getComponentIndex(cast this); - if (index == 0) { - return; - } - - parentComponent.setComponentIndex(cast this, index - 1); - } - - /** - * Sets this component's z-index to `parentComponent.numComponents - 1`, - * effectively putting it after in front of every single one of the parent's children. - */ - public function moveComponentToFront() { - if (parentComponent == null || parentComponent.numComponents <= 1) { - return; - } - - parentComponent.setComponentIndex(cast this, parentComponent.numComponents - 1); - } - - /** - * Moves this component to the front of the child component in front of it. - */ - public function moveComponentFrontward() { - if (parentComponent == null || parentComponent.numComponents <= 1) { - return; - } - - var index = parentComponent.getComponentIndex(cast this); - if (index == parentComponent.numComponents - 1) { - return; - } - - parentComponent.setComponentIndex(cast this, index + 1); - } - - /** - * Gets the child component at the bottom of this component - */ - public var bottomComponent(get, null):Component; - private function get_bottomComponent():Component { - if (_children == null || _children.length == 0) { - return null; - } - return cast _children[0]; - } - - /** - * Gets the child component at the top of this component - */ - public var topComponent(get, null):Component; - private function get_topComponent():Component { - if (_children == null || _children.length == 0) { - return null; - } - return cast _children[_children.length - 1]; - } - - private function postCloneComponent(c:Component):Void { - } - - //*********************************************************************************************************** - // Layout related - //*********************************************************************************************************** - // not idea place for them, but ComponentValidation needs them - @:noCompletion private var _layout:Layout = null; - - @:noCompletion private var _layoutLocked:Bool = false; - - //*********************************************************************************************************** - // Style related - //*********************************************************************************************************** - @:noCompletion private var _style:Style = null; - - //*********************************************************************************************************** - // General - //*********************************************************************************************************** - - /** - * The text displayed inside this component. - */ - @:clonable @:behaviour(ComponentTextBehaviour) public var text:String; - /** - * The text displayed inside the label. - * - * `value` is used as a universal way to access the "core" value a component is based on. - * in this case, its the component's text. - */ - @:clonable @:behaviour(ComponentValueBehaviour) public var value:Dynamic; - - @:noCompletion private var _id:String = null; - /** - * This component's identifier. - */ - @:clonable public var id(get, set):String; - private function get_id():String { - return _id; - } - private function set_id(value:String):String { - if (_id != value) { - _id = value; - //invalidate(InvalidationFlags.STYLE); - //invalidateDisplay(); - } - return _id; - } -} - -//*********************************************************************************************************** -// Default behaviours -//*********************************************************************************************************** -@:dox(hide) @:noCompletion -@:access(haxe.ui.core.Component) -class ComponentTextBehaviour extends DefaultBehaviour { - public override function set(value:Variant) { - if (value == _value) { - return; - } - - _value = value; - - super.set(value); - } -} - -@:dox(hide) @:noCompletion -@:access(haxe.ui.core.Component) -class ComponentDisabledBehaviour extends DefaultBehaviour { - public function new(component:Component) { - super(component); - _value = false; - } - - public override function set(value:Variant) { - if (value == _value) { - return; - } - _value = value; - if (value != null && value.isNull == false) { - _component.disableInteractivity(value, true, true); - } - } - - public override function get():Variant { - return _component.hasClass(":disabled"); - } -} - -@:dox(hide) @:noCompletion -@:access(haxe.ui.core.Component) -class ComponentValueBehaviour extends ValueBehaviour { - public override function set(value:Variant) { - if (value == _value) { - return; - } - - _value = value; - _component.text = value; - } - - public override function get():Variant { - return _value; - } - - public override function getDynamic():Dynamic { - return Variant.toDynamic(_value); - } -} - -@:dox(hide) @:noCompletion -@:access(haxe.ui.core.Component) -class ComponentToolTipBehaviour extends DataBehaviour { - public override function validateData() { - if (_value == null || _value.isNull) { - ToolTipManager.instance.unregisterTooltip(_component); - } else { - ToolTipManager.instance.registerTooltip(_component, { - tipData: Variant.toDynamic(_value), - renderer: cast _component.tooltipRenderer - }); - } - } - - public override function setDynamic(value:Dynamic) { - ToolTipManager.instance.unregisterTooltip(_component); - if (value != null) { - ToolTipManager.instance.registerTooltip(_component, { - tipData: value, - renderer: cast _component.tooltipRenderer - }); - } - } - - public override function getDynamic():Dynamic { - var options = ToolTipManager.instance.getTooltipOptions(_component); - if (options == null) { - return null; - } - return options.tipData; - } -} - -@:dox(hide) @:noCompletion -@:access(haxe.ui.core.Component) -class ComponentToolTipRendererBehaviour extends DataBehaviour { - public override function validateData() { - if (_value == null || _value.isNull) { - ToolTipManager.instance.updateTooltipRenderer(_component, null); - } else { - ToolTipManager.instance.updateTooltipRenderer(_component, cast _value.toComponent()); - } - } -} diff --git a/haxe/ui/core/ComponentEvents.hx b/haxe/ui/core/ComponentEvents.hx deleted file mode 100644 index 0724f289b..000000000 --- a/haxe/ui/core/ComponentEvents.hx +++ /dev/null @@ -1,308 +0,0 @@ -package haxe.ui.core; - -import haxe.ui.Toolkit; -import haxe.ui.events.Events; -import haxe.ui.events.KeyboardEvent; -import haxe.ui.events.MouseEvent; -import haxe.ui.events.UIEvent; -import haxe.ui.util.EventMap; -import haxe.ui.util.FunctionArray; - -class ComponentEvents extends ComponentContainer { - public function new() { - super(); - } - - @:noCompletion private var _internalEvents:Events = null; - @:noCompletion private var _internalEventsClass:Class = null; - private function registerInternalEvents(eventsClass:Class = null, reregister:Bool = false) { - if (_internalEvents == null && eventsClass != null) { - _internalEvents = Type.createInstance(eventsClass, [this]); - _internalEvents.register(); - } if (reregister == true && _internalEvents != null) { - _internalEvents.register(); - } - } - private function unregisterInternalEvents() { - if (_internalEvents == null) { - return; - } - _internalEvents.unregister(); - _internalEvents = null; - } - - //*********************************************************************************************************** - // Events - //*********************************************************************************************************** - @:noCompletion private var __events:EventMap; - - /** - Register a listener for a certain `UIEvent` - **/ - @:dox(group = "Event related properties and methods") - public function registerEvent(type:String, listener:T->Void, priority:Int = 0) { - if (cast(this, Component).hasClass(":mobile") && (type == MouseEvent.MOUSE_OVER || type == MouseEvent.MOUSE_OUT)) { - return; - } - - if (disabled == true && isInteractiveEvent(type) == true) { - if (_disabledEvents == null) { - _disabledEvents = new EventMap(); - } - _disabledEvents.add(type, listener, priority); - return; - } - - if (__events == null) { - __events = new EventMap(); - } - if (__events.add(type, listener, priority) == true) { - mapEvent(type, _onMappedEvent); - } - } - - /** - Returns if this component has a certain event and listener - **/ - @:dox(group = "Event related properties and methods") - public function hasEvent(type:String, listener:T->Void = null):Bool { - if (__events == null) { - return false; - } - return __events.contains(type, listener); - } - - /** - Unregister a listener for a certain `UIEvent` - **/ - @:dox(group = "Event related properties and methods") - public function unregisterEvent(type:String, listener:T->Void) { - if (_disabledEvents != null && !_interactivityDisabled) { - _disabledEvents.remove(type, listener); - } - - if (__events != null) { - if (__events.remove(type, listener) == true) { - unmapEvent(type, _onMappedEvent); - } - } - } - - /** - Dispatch a certain `UIEvent` - **/ - @:dox(group = "Event related properties and methods") - public override function dispatch(event:UIEvent) { - if (event != null) { - if (__events != null) { - __events.invoke(event.type, event, cast(this, Component)); // TODO: avoid cast - } - - if (event.bubble == true && event.canceled == false && parentComponent != null) { - parentComponent.dispatch(event); - } - } - } - - private function dispatchRecursively(event:UIEvent) { - dispatch(event); - for (child in childComponents) { - child.dispatchRecursively(event); - } - } - - private function dispatchRecursivelyWhen(event:UIEvent, condition:Component->Bool) { - if (condition(cast this) == true) { - dispatch(event); - } - for (child in childComponents) { - if (condition(child) == true) { - child.dispatchRecursivelyWhen(event, condition); - } - } - } - - @:noCompletion - private function _onMappedEvent(event:UIEvent) { - dispatch(event); - } - - @:noCompletion private var _disabledEvents:EventMap; - private static var INTERACTIVE_EVENTS:Array = [ - MouseEvent.MOUSE_MOVE, MouseEvent.MOUSE_OVER, MouseEvent.MOUSE_OUT, MouseEvent.MOUSE_DOWN, - MouseEvent.MOUSE_UP, MouseEvent.MOUSE_WHEEL, MouseEvent.CLICK, MouseEvent.DBL_CLICK, KeyboardEvent.KEY_DOWN, - KeyboardEvent.KEY_UP - ]; - - private function isInteractiveEvent(type:String):Bool { - return INTERACTIVE_EVENTS.indexOf(type) != -1; - } - - private function disableInteractiveEvents(disable:Bool) { - if (disable == true) { - if (__events != null) { - for (eventType in __events.keys()) { - if (!isInteractiveEvent(eventType)) { - continue; - } - var listeners:FunctionArrayVoid> = __events.listeners(eventType); - if (listeners != null) { - for (listener in listeners.copy()) { - if (_disabledEvents == null) { - _disabledEvents = new EventMap(); - } - _disabledEvents.add(eventType, listener); - unregisterEvent(eventType, listener); - } - } - } - } - } else { - if (_disabledEvents != null) { - for (eventType in _disabledEvents.keys()) { - var listeners:FunctionArrayVoid> = _disabledEvents.listeners(eventType); - if (listeners != null) { - for (listener in listeners.copy()) { - registerEvent(eventType, listener); - } - } - } - _disabledEvents = null; - } - } - } - - @:noCompletion private var _interactivityDisabled:Bool = false; - @:noCompletion private var _interactivityDisabledCounter:Int = 0; - #if haxeui_html5 - private var _lastCursor:String = null; - #end - private function disableInteractivity(disable:Bool, recursive:Bool = true, updateStyle:Bool = false, force:Bool = false) { // You might want to disable interactivity but NOT actually disable visually - if (force == true) { - _interactivityDisabledCounter = 0; - } - if (disable == true) { - _interactivityDisabledCounter++; - } else { - _interactivityDisabledCounter--; - } - - if (_interactivityDisabledCounter > 0 && _interactivityDisabled == false) { - _interactivityDisabled = true; - if (updateStyle == true) { - cast(this, Component).swapClass(":disabled", ":hover"); - } - handleDisabled(true); - disableInteractiveEvents(true); - dispatch(new UIEvent(UIEvent.DISABLED)); - #if haxeui_html5 - _lastCursor = cast(this, Component).element.style.cursor; - cast(this, Component).element.style.removeProperty("cursor"); - #end - } else if (_interactivityDisabledCounter < 1 && _interactivityDisabled == true) { - _interactivityDisabled = false; - if (updateStyle == true) { - cast(this, Component).removeClass(":disabled"); - } - handleDisabled(false); - disableInteractiveEvents(false); - dispatch(new UIEvent(UIEvent.ENABLED)); - #if haxeui_html5 - if (_lastCursor != null) { - cast(this, Component).element.style.cursor = _lastCursor; - } - #end - } - - if (recursive == true) { - for (child in childComponents) { - child.disableInteractivity(disable, recursive, updateStyle); - } - } - } - - private function unregisterEvents() { - if (__events != null) { - var copy:Array = []; - for (eventType in __events.keys()) { - copy.push(eventType); - } - for (eventType in copy) { - var listeners = __events.listeners(eventType); - if (listeners != null) { - for (listener in listeners) { - if (listener != null) { - if (__events.remove(eventType, listener) == true) { - unmapEvent(eventType, _onMappedEvent); - } - } - } - } - } - } - } - - @:noCompletion private var _pausedEvents:MapVoid>> = null; - public function pauseEvent(type:String, recursive:Bool = false) { - if (__events == null || __events.contains(type) == false) { - return; - } - - if (_pausedEvents == null) { - _pausedEvents = new MapVoid>>(); - } - - var pausedList = _pausedEvents.get(type); - if (pausedList == null) { - pausedList = new ArrayVoid>(); - _pausedEvents.set(type, pausedList); - } - - var listeners = __events.listeners(type).copy(); - for (l in listeners) { - pausedList.push(l); - unregisterEvent(type, l); - } - - if (recursive == true) { - for (c in childComponents) { - c.pauseEvent(type, recursive); - } - } - } - - public function resumeEvent(type:String, recursive:Bool = false) { - if (__events == null) { - return; - } - - if (_pausedEvents == null) { - return; - } - - if (_pausedEvents.exists(type) == false) { - return; - } - - Toolkit.callLater(function() { - var pausedList = _pausedEvents.get(type); - for (l in pausedList) { - registerEvent(type, l); - } - _pausedEvents.remove(type); - }); - - if (recursive == true) { - for (c in childComponents) { - c.resumeEvent(type, recursive); - } - } - } - - private function mapEvent(type:String, listener:UIEvent->Void) { - } - - private function unmapEvent(type:String, listener:UIEvent->Void) { - - } -} \ No newline at end of file diff --git a/haxe/ui/core/ComponentLayout.hx b/haxe/ui/core/ComponentLayout.hx deleted file mode 100644 index 754edf6ca..000000000 --- a/haxe/ui/core/ComponentLayout.hx +++ /dev/null @@ -1,26 +0,0 @@ -package haxe.ui.core; - -import haxe.ui.styles.Style; - -class ComponentLayout extends ComponentValidation { - //*********************************************************************************************************** - // Layout related - //*********************************************************************************************************** - - //*********************************************************************************************************** - // Style related - //*********************************************************************************************************** - /** - The calculated style of this component - **/ - @:dox(group = "Style related properties and methods") - public var style(get, set):Style; - private function get_style():Style { - return _style; - } - - private function set_style(value):Style { - _style = value; - return value; - } -} \ No newline at end of file diff --git a/haxe/ui/core/ComponentValidation.hx b/haxe/ui/core/ComponentValidation.hx deleted file mode 100644 index 2894f2435..000000000 --- a/haxe/ui/core/ComponentValidation.hx +++ /dev/null @@ -1,315 +0,0 @@ -package haxe.ui.core; - -import haxe.ui.validation.InvalidationFlags; -import haxe.ui.validation.ValidationManager; - -class ComponentValidation extends ComponentEvents { - @:noCompletion private var _invalidationFlags:Map = new Map(); - @:noCompletion private var _delayedInvalidationFlags:Map = new Map(); - @:noCompletion private var _isAllInvalid:Bool = false; - @:noCompletion private var _isValidating:Bool = false; - @:noCompletion private var _isInitialized:Bool = false; - @:noCompletion private var _isDisposed:Bool = false; - @:noCompletion private var _invalidateCount:Int = 0; - - @:noCompletion private var _depth:Int = -1; - @:dox(group = "Internal") - public var depth(get, set):Int; - private function get_depth():Int { - return _depth; - } - private function set_depth(value:Int):Int { - if (_depth == value) { - return value; - } - - _depth = value; - - return value; - } - - /** - Check if the component is invalidated with some `flag`. - **/ - @:dox(group = "Invalidation related properties and methods") - public function isComponentInvalid(flag:String = InvalidationFlags.ALL):Bool { - if (_isAllInvalid == true) { - return true; - } - - if (flag == InvalidationFlags.ALL) { - for (value in _invalidationFlags) { - return true; - } - - return false; - } - - return _invalidationFlags.exists(flag); - } - - /** - Invalidate this components with the `InvalidationFlags` indicated. If it hasn't parameter then the component will be invalidated completely. - **/ - @:dox(group = "Invalidation related properties and methods") - public function invalidateComponent(flag:String = InvalidationFlags.ALL, recursive:Bool = false) { - if (_componentReady == false) { - return; //it should be added into the queue later - } - - var isAlreadyInvalid:Bool = isComponentInvalid(flag); - var isAlreadyDelayedInvalid:Bool = false; - if (_isValidating == true) { - for (value in _delayedInvalidationFlags) { - isAlreadyDelayedInvalid = true; - break; - } - } - - if (flag == InvalidationFlags.ALL) { - if (_isValidating == true) { - _delayedInvalidationFlags.set(InvalidationFlags.ALL, true); - } else { - _isAllInvalid = true; - } - } else { - if (_isValidating == true) { - _delayedInvalidationFlags.set(flag, true); - } else if (flag != InvalidationFlags.ALL && !_invalidationFlags.exists(flag)) { - _invalidationFlags.set(flag, true); - } - } - - if (_isValidating == true) { - //it is already in queue - if (isAlreadyDelayedInvalid == true) { - return; - } - - _invalidateCount++; - - //we track the invalidate count to check if we are in an infinite loop or serious bug because it affects performance - if (this._invalidateCount >= 10) { - throw 'The validation queue returned too many times during validation. This may be an infinite loop. Try to avoid doing anything that calls invalidate() during validation.'; - } - - ValidationManager.instance.add(cast(this, Component)); // TODO: avoid cast - return; - } else if (isAlreadyInvalid == true) { - if (recursive == true) { - for (child in childComponents) { - child.invalidateComponent(flag, recursive); - } - } - return; - } - - _invalidateCount = 0; - ValidationManager.instance.add(cast(this, Component)); // TODO: avoid cast - - if (recursive == true) { - for (child in childComponents) { - child.invalidateComponent(flag, recursive); - } - } - } - - /** - Invalidate the data of this component - **/ - @:dox(group = "Invalidation related properties and methods") - public inline function invalidateComponentData(recursive:Bool = false) { - invalidateComponent(InvalidationFlags.DATA, recursive); - } - - /** - Invalidate this components layout, may result in multiple calls to `invalidateDisplay` and `invalidateLayout` of its children - **/ - @:dox(group = "Invalidation related properties and methods") - public inline function invalidateComponentLayout(recursive:Bool = false) { - if (_layout == null || _layoutLocked == true) { - return; - } - invalidateComponent(InvalidationFlags.LAYOUT, recursive); - } - - /** - Invalidate the position of this component - **/ - @:dox(group = "Invalidation related properties and methods") - public inline function invalidateComponentPosition(recursive:Bool = false) { - invalidateComponent(InvalidationFlags.POSITION, recursive); - } - - /** - Invalidate the visible aspect of this component - **/ - @:dox(group = "Invalidation related properties and methods") - public inline function invalidateComponentDisplay(recursive:Bool = false) { - invalidateComponent(InvalidationFlags.DISPLAY, recursive); - } - - /** - Invalidate and recalculate this components style, may result in a call to `invalidateDisplay` - **/ - @:dox(group = "Invalidation related properties and methods") - public inline function invalidateComponentStyle(force:Bool = false, recursive:Bool = false) { - invalidateComponent(InvalidationFlags.STYLE, recursive); - if (force == true) { - _style = null; - } - } - - /** - This method validates the tasks pending in the component. - **/ - @:dox(group = "Invalidation related properties and methods") - public function validateComponent(nextFrame:Bool = true) { - if (_componentReady == false || - _isDisposed == true || //we don't want to validate disposed components, but they may have been left in the queue. - _isValidating == true || //we were already validating, the existing validation will continue. - isComponentInvalid() == false) { //if none is invalid, exit. - return; - } - - var isInitialized = _isInitialized; - if (isInitialized == false) { - initializeComponent(); - } - - _isValidating = true; - - validateComponentInternal(nextFrame); - validateInitialSize(isInitialized); - - #if (haxe_ver < 4) - _invalidationFlags = new Map(); - #else - _invalidationFlags.clear(); - #end - - _isAllInvalid = false; - - for (flag in _delayedInvalidationFlags.keys()) { - if (flag == InvalidationFlags.ALL) { - _isAllInvalid = true; - } else { - _invalidationFlags.set(flag, true); - } - } - #if (haxe_ver < 4) - _delayedInvalidationFlags = new Map(); - #else - _delayedInvalidationFlags.clear(); - #end - - _isValidating = false; - } - - /** - Validate this component and its children on demand. - **/ - @:dox(group = "Invalidation related properties and methods") - public function validateNow() { - for (child in childComponents) { - child.validateNow(); - } - invalidateComponent(); - syncComponentValidation(false); - } - - /** - Validate this component and its children on demand. - **/ - @:dox(group = "Invalidation related properties and methods") - public function syncComponentValidation(nextFrame:Bool = true) { - var count:Int = 0; - while (isComponentInvalid()) { - validateComponent(nextFrame); - - for (child in childComponents) { - child.syncComponentValidation(nextFrame); - } - - if (++count >= 10) { - if (this._isDisposed) { - #if debug - trace('There was a problem validating this component as it has already been destroyed (${Type.getClassName(Type.getClass(this))}#${this.id})'); - #end - throw 'There was a problem validating this component as it has already been destroyed (${Type.getClassName(Type.getClass(this))}#${this.id})'; - } else { - #if debug - trace('The syncValidation returned too many times during validation. This may be an infinite loop. Try to avoid doing anything that calls invalidate() during validation (${Type.getClassName(Type.getClass(this))}#${this.id}).'); - #end - throw 'The syncValidation returned too many times during validation. This may be an infinite loop. Try to avoid doing anything that calls invalidate() during validation (${Type.getClassName(Type.getClass(this))}#${this.id}).'; - } - } - } - } - - private function validateComponentInternal(nextFrame:Bool = true) { - var dataInvalid = isComponentInvalid(InvalidationFlags.DATA); - var styleInvalid = isComponentInvalid(InvalidationFlags.STYLE); - var textDisplayInvalid = isComponentInvalid(InvalidationFlags.TEXT_DISPLAY) && hasTextDisplay(); - var textInputInvalid = isComponentInvalid(InvalidationFlags.TEXT_INPUT) && hasTextInput(); - var imageDisplayInvalid = isComponentInvalid(InvalidationFlags.IMAGE_DISPLAY) && hasImageDisplay(); - var positionInvalid = isComponentInvalid(InvalidationFlags.POSITION); - var displayInvalid = isComponentInvalid(InvalidationFlags.DISPLAY); - var layoutInvalid = isComponentInvalid(InvalidationFlags.LAYOUT) && _layoutLocked == false; - - if (dataInvalid) { - validateComponentData(); - } - - if (styleInvalid) { - validateComponentStyle(); - } - - if (textDisplayInvalid) { - getTextDisplay().validateComponent(); - } - - if (textInputInvalid) { - getTextInput().validateComponent(); - } - - if (imageDisplayInvalid) { - getImageDisplay().validateComponent(); - } - - if (positionInvalid) { - validateComponentPosition(); - } - - if (layoutInvalid) { - displayInvalid = validateComponentLayout() || displayInvalid; - } - - if (displayInvalid || styleInvalid) { - ValidationManager.instance.addDisplay(cast(this, Component), nextFrame); //Update the display from all objects at the same time. Avoids UI flashes. - } - } - - private function initializeComponent() { - - } - - private function validateInitialSize(isInitialized:Bool) { - - } - - private function validateComponentData() { - } - - private function validateComponentLayout():Bool { - return false; - } - - private function validateComponentStyle() { - - } - - private function validateComponentPosition() { - - } -} \ No newline at end of file diff --git a/haxe/ui/macros/Macros.hx b/haxe/ui/macros/Macros.hx index 0b13150a7..96c7a73e9 100644 --- a/haxe/ui/macros/Macros.hx +++ b/haxe/ui/macros/Macros.hx @@ -424,7 +424,7 @@ class Macros { var stopTimer = Context.timer("build clonable"); #end - var useSelf:Bool = (builder.fullPath == "haxe.ui.core.ComponentContainer"); + var useSelf:Bool = (builder.fullPath == "haxe.ui.backend.ComponentBase"); var cloneFn = builder.findFunction("cloneComponent"); if (cloneFn == null) { // add new clone fn