haxe/ui/core/Screen.hx
package haxe.ui.core;
import haxe.ui.backend.ScreenImpl;
import haxe.ui.core.Component;
import haxe.ui.events.MouseEvent;
import haxe.ui.events.UIEvent;
import haxe.ui.focus.FocusManager;
import haxe.ui.util.EventMap;
#if (haxe_ver >= 4.2)
import Std.isOfType;
#else
import Std.is as isOfType;
#end
#if haxeui_expose_all
@:expose
#end
class Screen extends ScreenImpl {
private static var _instance:Screen;
/**
* References the main application's screen, in a cross-framework way.
*/
public static var instance(get, never):Screen;
private static function get_instance():Screen {
if (_instance == null) {
_instance = new Screen();
}
return _instance;
}
//***********************************************************************************************************
// Instance
//***********************************************************************************************************
private var _eventMap:EventMap = new EventMap();
/**
* The `x` position of the mouse on screen.
*
* A lower value means the mouse is at the left side of the screen,
* whie a higher value means the mouse is at the right side of the screen.
*/
public var currentMouseX:Null<Float> = null;
/**
* The `y` position of the mouse on screen.
*
* A lower value means the mouse is closer to the top of the screen,
* whie a higher value means the mouse is closer to the bottom of the screen.
*/
public var currentMouseY:Null<Float> = null;
/**
* Creates a new `Screen`.
*
* Usually, you wouldn't want to create a screen yourself, but to use the one in `Screen.instance`.
* Double check if thats what your'e trying to do.
*/
public function new() {
super();
registerEvent(MouseEvent.MOUSE_MOVE, function(e:MouseEvent) {
currentMouseX = e.screenX;
currentMouseY = e.screenY;
});
}
/**
* Adds a component/container to the screen.
*
* When using this via `Screen.instance.addComponent()`, This method acts as some sort of a cross-framework way
* to draw components/containers onto the main application's screen.
*
* @param component The component to add to the screen.
* @return The added component.
*/
public override function addComponent(component:Component):Component {
var wasReady = component.isReady;
@:privateAccess component._hasScreen = true;
super.addComponent(component);
#if !(haxeui_javafx || haxeui_android)
component.ready();
#end
if (rootComponents.indexOf(component) == -1) {
rootComponents.push(component);
}
FocusManager.instance.pushView(component);
if (component.hasEvent(UIEvent.RESIZE, _onRootComponentResize) == false) {
component.registerEvent(UIEvent.RESIZE, _onRootComponentResize);
}
if (wasReady && component.hidden == false) {
component.dispatch(new UIEvent(UIEvent.SHOWN));
}
return component;
}
/**
* Removes a component/container from the screen.
*
* When using this via `Screen.instance.removeComponent()`,
* This method acts as some sort of a cross-framework way
* to remove components/containers from the main application's screen.
*
* @param component The component to add to the screen.
* @return The added component.
*/
public override function removeComponent(component:Component, dispose:Bool = true, invalidate:Bool = true):Component {
if (component == null) {
return null;
}
if (@:privateAccess !component._allowDispose) {
dispose = false;
}
if (rootComponents.indexOf(component) == -1) {
if (dispose) {
component.disposeComponent();
}
return component;
}
@:privateAccess component._hasScreen = false;
super.removeComponent(component, dispose);
component.depth = -1;
rootComponents.remove(component);
FocusManager.instance.removeView(component);
component.unregisterEvent(UIEvent.RESIZE, _onRootComponentResize);
if (dispose) {
component.disposeComponent();
} else {
component.dispatch(new UIEvent(UIEvent.HIDDEN));
// sometimes (on some backends, like browser), mouse out doesnt fire when removing from screen
component.removeClass(":hover", false, true);
}
return component;
}
public override function containsComponent(child:Component):Bool {
if (child == null) {
return false;
}
for (rootComponent in rootComponents) {
if (rootComponent == child) {
return true;
}
}
return false;
}
/**
* Sets the index of a component, essentially moving it forwards/backwards,
* or, in front/behind other components.
*
* For example, setting the index of a child of a `VBox` to 0 will put that child at the top of the `VBox`,
* "behind" the rest of the children.
*
* @param child The component to move.
* @param index The index to move that component to.
* @return The moved component.
*/
public function setComponentIndex(child:Component, index:Int):Component {
if (index >= 0 && index <= rootComponents.length) {
handleSetComponentIndex(child, index);
rootComponents.remove(child);
rootComponents.insert(index, child);
}
return child;
}
/**
* Moves a component to the front of the screen.
*
* @param child The component to move to the front of the screen.
*/
public function moveComponentToFront(child:Component) {
if (rootComponents.indexOf(child) != -1) {
setComponentIndex(child, rootComponents.length - 1);
}
}
/**
* Finds a specific child in the whole display tree (recursively if desired) and can optionally cast the result.
*
* @param criteria The criteria by which to search, the interpretation of this is defined using `searchType` (the default search type is `id`).
* @param type The component class you wish to cast the result to (defaults to `null`).
* @param recursive Whether to search this components children and all its children's children till it finds a match (the default depends on the `searchType` param. If `searchType` is `id` the default is *true* otherwise it is `false`)
* @param searchType Allows you specify how to consider a child a match (defaults to *id*), can be either: **`id`** - The first component that has the id specified in `criteria` will be considered a match, *or*, **`css`** - The first component that contains a style name specified by `criteria` will be considered a match.
* @return The found component, or `null` if no component was found.
*/
public function findComponent<T:Component>(criteria:String = null, type:Class<T> = null, recursive:Null<Bool> = null, searchType:String = "id"):Null<T> {
for (rootComponent in rootComponents) {
var result = rootComponent.findComponent(criteria, type, recursive, searchType);
if (result != null) {
return cast result;
}
}
return null;
}
/**
* Lists components under a specific point in global, screen coordinates.
*
* Note: this function will return *every single* components at a specific point,
* even if they have no backgrounds, or haven't got anything drawn onto them.
*
* @param screenX The global, on-screen `x` position of the point to check for components under
* @param screenY The global, on-screen `y` position of the point to check for components under
* @param type Used to filter all components that aren't of a specific type. `null` by default, which means no filter is applied.
* @return An array of all components that overlap the "global" position `(x, y)`
*/
public function findComponentsUnderPoint<T:Component>(screenX:Null<Float>, screenY:Null<Float>, type:Class<T> = null):Array<Component> {
if (screenX == null || screenY == null) {
return [];
}
var c:Array<Component> = [];
for (r in rootComponents) {
if (r.hitTest(screenX, screenY)) {
var match = true;
if (type != null && isOfType(r, type) == false) {
match = false;
}
if (match == true) {
c.push(r);
}
}
c = c.concat(r.findComponentsUnderPoint(screenX, screenY, type));
}
return c;
}
/**
* Finds out if there is a component under a specific point in global coordinates.
*
* @param screenX The global, on-screen `x` position of the point to check for components under
* @param screenY The global, on-screen `y` position of the point to check for components under
* @param type Used to filter all components that aren't of a specific type. `null` by default, which means no filter is applied.
* @return `true` if there is a component that overlaps the global position `(x, y)`, `false` otherwise.
*/
public function hasComponentUnderPoint<T:Component>(screenX:Null<Float>, screenY:Null<Float>, type:Class<T> = null):Bool {
if (screenX == null || screenY == null) {
return false;
}
for (r in rootComponents) {
if (r.hasComponentUnderPoint(screenX, screenY, type) == true) {
return true;
}
}
return false;
}
/**
* Lists components under a specific point in global, screen coordinates.
*
* Note: this function will only return components "solid" components - components that have
* some sort of a background/image, and are not transparent.
*
* @param screenX The global, on-screen `x` position of the point to check for components under
* @param screenY The global, on-screen `y` position of the point to check for components under
* @param type Used to filter all components that aren't of a specific type. `null` by default, which means no filter is applied.
* @return An array of all solid components that overlap the "global" position `(x, y)`
*/
public function findSolidComponentUnderPoint<T:Component>(screenX:Null<Float>, screenY:Null<Float>, type:Class<T> = null):Array<Component> {
if (screenX == null || screenY == null) {
return [];
}
var solidComponents = [];
var components = findComponentsUnderPoint(screenX, screenY, type);
for (c in components) {
if (c.isComponentSolid) {
solidComponents.push(c);
}
}
return solidComponents;
}
/**
* Finds out if there is a solid component under a specific point in global coordinates.
*
* Note: a solid component is a component that has
* some sort of a background/image, and is not transparent.
*
* @param screenX The global, on-screen `x` position of the point to check for components under
* @param screenY The global, on-screen `y` position of the point to check for components under
* @param type Used to filter all components that aren't of a specific type. `null` by default, which means no filter is applied.
* @return `true` if there is a solid component that overlaps the global position `(x, y)`, `false` otherwise.
*/
public function hasSolidComponentUnderPoint<T:Component>(screenX:Null<Float>, screenY:Null<Float>, type:Class<T> = null):Bool {
if (screenX == null || screenY == null) {
return false;
}
return (findSolidComponentUnderPoint(screenX, screenY, type).length > 0);
}
//***********************************************************************************************************
// Events
//***********************************************************************************************************
/**
* Adds an event listener, that listens to a certain type of event.
*
* @param type The name of the event to listen to
* @param listener a function with one argument that returns nothing
* @param priority A higher value will make this event dispatch "sooner", while a lower value does the opposite.
*/
public function registerEvent(type:String, listener:Dynamic->Void, priority:Int = 0) {
if (supportsEvent(type) == true) {
if (_eventMap.add(type, listener, priority) == true) {
mapEvent(type, _onMappedEvent);
}
} else {
#if debug
trace('WARNING: Screen event "${type}" not supported');
#end
}
}
public function hasEvent(type:String, listener:Dynamic->Void):Bool {
return _eventMap.contains(type, listener);
}
/**
* Removes an event listener from a specific type of event.
*
* @param type The name of the event to listen to
* @param listener a function with one argument that returns nothing
*/
public function unregisterEvent(type:String, listener:Dynamic->Void) {
if (_eventMap.remove(type, listener) == true) {
unmapEvent(type, _onMappedEvent);
}
}
private function _onMappedEvent(event:UIEvent) {
_eventMap.invoke(event.type, event);
}
}