haxe/ui/containers/TabView.hx
package haxe.ui.containers;
import haxe.ui.behaviours.Behaviour;
import haxe.ui.behaviours.DataBehaviour;
import haxe.ui.behaviours.DefaultBehaviour;
import haxe.ui.components.Button;
import haxe.ui.components.TabBar;
import haxe.ui.core.Component;
import haxe.ui.core.CompositeBuilder;
import haxe.ui.events.Events;
import haxe.ui.events.UIEvent;
import haxe.ui.geom.Size;
import haxe.ui.layouts.DefaultLayout;
import haxe.ui.layouts.LayoutFactory;
import haxe.ui.styles.Style;
import haxe.ui.util.Variant;
@:composite(Builder, Events, Layout)
class TabView extends Component {
//***********************************************************************************************************
// Public API
//***********************************************************************************************************
@:behaviour(PageIndex, -1) public var pageIndex:Int;
@:behaviour(SelectedPage, null) public var selectedPage:Component;
@:behaviour(TabPosition) public var tabPosition:String;
@:behaviour(PageCount) public var pageCount:Int;
@:behaviour(Closable, false) public var closable:Bool;
@:behaviour(ButtonWidth, null) public var buttonWidth:Null<Float>;
@:behaviour(ButtonHeight, null) public var buttonHeight:Null<Float>;
@:call(RemovePage) public function removePage(index:Int);
@:call(GetPage) public function getPage(index:Int):Component;
@:call(GetPageById) public function getPageById(pageId:String):Component;
@:call(RemoveAllPages) public function removeAllPages();
}
//***********************************************************************************************************
// Composite Layout
//***********************************************************************************************************
@:dox(hide) @:noCompletion
private class Layout extends DefaultLayout {
private override function repositionChildren() {
var tabs:TabBar = component.findComponent(TabBar, false);
var content:Box = component.findComponent(Box, false);
if (tabs == null || content == null) {
return;
}
if (component.hasClass(":bottom")) {
content.left = paddingLeft;
content.top = paddingTop;
tabs.left = paddingLeft;
if (tabs.height != 0) {
tabs.top = component.height - tabs.height - paddingBottom + marginTop(tabs);
}
} else {
tabs.left = paddingLeft;
tabs.top = paddingTop + marginTop(tabs);
content.left = paddingLeft;
if (tabs.height != 0) {
content.top = tabs.top + tabs.height - marginTop(tabs) + marginTop(content);
}
}
}
private override function resizeChildren() {
var tabs:TabBar = component.findComponent(TabBar, false);
var content:Box = component.findComponent(Box, false);
if (tabs == null || content == null) {
return;
}
var usableSize = usableSize;
var tabsWidthModifier:Float = 0;
if (tabs.style == null) {
tabs.validateNow();
}
if (tabs.marginRight != null) {
tabsWidthModifier = tabs.marginRight;
}
tabs.width = usableSize.width - tabsWidthModifier;
if (component.autoHeight == false) {
content.height = usableSize.height + 1;
}
if (component.autoWidth == false) {
content.width = component.width;
}
}
private override function get_usableSize():Size {
var size:Size = super.get_usableSize();
var tabs:TabBar = component.findComponent(TabBar, false);
if (tabs != null && tabs.componentHeight != null) {
size.height -= tabs.componentHeight;
}
return size;
}
public override function calcAutoSize(exclusions:Array<Component> = null):Size {
var tabs:TabBar = component.findComponent(TabBar, false);
var content:Box = component.findComponent(Box, false);
if (tabs == null || content == null) {
return super.calcAutoSize(exclusions);
}
var size = super.calcAutoSize(exclusions);
if (content.autoHeight) {
var modifier = 0 - marginTop(tabs);
if (component.hasClass(":bottom")) {
modifier = -1 - marginTop(tabs);
}
size.height = tabs.height + content.height + modifier;
}
return size;
}
}
//***********************************************************************************************************
// Behaviours
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class Closable extends DataBehaviour {
public override function validateData() {
if (_component.native == true) {
return;
}
var builder:Builder = cast(_component._compositeBuilder, Builder);
builder._tabs.closable = _value;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class PageIndex extends DataBehaviour {
public override function set(value:Variant) {
if (value > -1) {
var builder:Builder = cast(_component._compositeBuilder, Builder);
builder._tabs.selectedIndex = value;
}
super.set(value);
}
public override function validateData() {
if (_component.native == true) {
return;
}
var builder:Builder = cast(_component._compositeBuilder, Builder);
if (_value < 0) {
return;
}
if (_value > builder._views.length - 1) {
_value = builder._views.length - 1;
return;
}
builder._tabs.selectedIndex = _value;
var view:Component = builder._views[_value.toInt()];
if (view != null) {
if (builder._currentView != null) {
//_content.removeComponent(_currentView, false);
builder._currentView.hide();
}
if (builder._content.getComponentIndex(view) == -1) {
builder._content.addComponent(view);
} else {
view.show();
}
builder._currentView = view;
}
_component.dispatch(new UIEvent(UIEvent.CHANGE));
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class SelectedPage extends DefaultBehaviour {
public override function get():Variant {
var tabview:TabView = cast(_component, TabView);
var builder:Builder = cast(_component._compositeBuilder, Builder);
var view:Component = builder._views[tabview.pageIndex];
return view;
}
public override function set(value:Variant) {
var tabview:TabView = cast(_component, TabView);
var builder:Builder = cast(_component._compositeBuilder, Builder);
var view:Component = value;
var viewIndex = builder._views.indexOf(view);
if (viewIndex != -1) {
tabview.pageIndex = viewIndex;
}
}
}
@:dox(hide) @:noCompletion
private class TabPosition extends DataBehaviour {
public override function validateData() {
if (_value == "bottom") {
_component.addClass(":bottom");
_component.findComponent(TabBar, false).componentTabIndex = 1;
_component.findComponent("tabview-content", Box, false).componentTabIndex = 0;
} else {
_component.removeClass(":bottom");
_component.findComponent(TabBar, false).componentTabIndex = 0;
_component.findComponent("tabview-content", Box, false).componentTabIndex = 1;
}
_component.findComponent(TabBar, false).tabPosition = _value;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class PageCount extends Behaviour {
public override function get():Variant {
var builder:Builder = cast(_component._compositeBuilder, Builder);
return builder._tabs.tabCount;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class RemovePage extends Behaviour {
public override function call(param:Any = null):Variant {
var builder:Builder = cast(_component._compositeBuilder, Builder);
var index:Int = param;
if (index < builder._views.length) {
builder._tabs.removeTab(index);
}
return null;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class GetPage extends Behaviour {
public override function call(param:Any = null):Variant {
var builder:Builder = cast(_component._compositeBuilder, Builder);
var index:Int = param;
var page:Component = null;
if (index < builder._views.length) {
page = builder._views[index];
}
return page;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class GetPageById extends Behaviour {
public override function call(param:Any = null):Variant {
var builder:Builder = cast(_component._compositeBuilder, Builder);
var id:String = param;
var page:Component = null;
for (view in builder._views) {
if (view.id == id) {
page = view;
break;
}
}
return page;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.containers.Builder)
private class RemoveAllPages extends Behaviour {
public override function call(param:Any = null):Variant {
var builder:Builder = cast(_component._compositeBuilder, Builder);
while (builder._views.length > 0) {
builder._tabs.removeTab(0);
}
cast(_component, TabView).pageIndex = -1;
builder._tabs.selectedIndex = -1;
return null;
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.components.Builder)
private class ButtonWidth extends DataBehaviour {
public override function validateData() {
var tabbar = _component.findComponent("tabview-tabs", TabBar);
if (tabbar != null) {
tabbar.buttonWidth = _value;
}
}
}
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.components.Builder)
private class ButtonHeight extends DataBehaviour {
public override function validateData() {
var tabbar = _component.findComponent("tabview-tabs", TabBar);
if (tabbar != null) {
tabbar.buttonHeight = _value;
}
}
}
//***********************************************************************************************************
// Events
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:access(haxe.ui.core.Component)
@:access(haxe.ui.components.TabViewBuilder)
@:access(haxe.ui.containers.Builder)
private class Events extends haxe.ui.events.Events {
private var _tabview:TabView;
public function new(tabview:TabView) {
super(tabview);
_tabview = tabview;
}
public override function register() {
var tabs:TabBar = _tabview.findComponent(TabBar, false);
if (tabs.hasEvent(UIEvent.CHANGE, onTabChanged) == false) {
tabs.registerEvent(UIEvent.CHANGE, onTabChanged);
}
if (tabs.hasEvent(UIEvent.BEFORE_CHANGE, onTabBeforeChanged) == false) {
tabs.registerEvent(UIEvent.BEFORE_CHANGE, onTabBeforeChanged);
}
if (tabs.hasEvent(UIEvent.BEFORE_CLOSE, onBeforeTabClosed) == false) {
tabs.registerEvent(UIEvent.BEFORE_CLOSE, onBeforeTabClosed);
}
if (tabs.hasEvent(UIEvent.CLOSE, onTabClosed) == false) {
tabs.registerEvent(UIEvent.CLOSE, onTabClosed);
}
}
public override function unregister() {
var tabs:TabBar = _tabview.findComponent(TabBar, false);
tabs.unregisterEvent(UIEvent.CHANGE, onTabChanged);
tabs.unregisterEvent(UIEvent.BEFORE_CHANGE, onTabBeforeChanged);
tabs.unregisterEvent(UIEvent.BEFORE_CLOSE, onBeforeTabClosed);
tabs.unregisterEvent(UIEvent.CLOSE, onTabClosed);
}
private function onBeforeTabClosed(event:UIEvent) {
_tabview.dispatch(event);
}
private function onTabClosed(event:UIEvent) {
var builder:Builder = cast(_tabview._compositeBuilder, Builder);
var view = builder._views[event.data];
builder._views.remove(view);
if (builder._content.getComponentIndex(view) != -1) {
builder._content.removeComponent(view);
}
_tabview.dispatch(new UIEvent(UIEvent.CLOSE, event.data));
}
private function onTabChanged(event:UIEvent) {
var tabs:TabBar = _tabview.findComponent(TabBar, false);
_tabview.pageIndex = -1;
_tabview.pageIndex = tabs.selectedIndex;
}
private function onTabBeforeChanged(event:UIEvent) {
_tabview.dispatch(event);
}
}
//***********************************************************************************************************
// Composite Builder
//***********************************************************************************************************
@:dox(hide) @:noCompletion
@:allow(haxe.ui.components.TabBar)
@:access(haxe.ui.core.Component)
private class Builder extends CompositeBuilder {
private var _tabview:TabView;
private var _tabs:TabBar;
private var _content:Box;
private var _currentView:Component = null;
private var _views:Array<Component> = [];
public function new(tabview:TabView) {
super(tabview);
_tabview = tabview;
}
public override function create() {
if (_content == null) {
_content = new Box();
_content.id = "tabview-content";
_content.addClass("tabview-content");
_content.layout = LayoutFactory.createFromName("vertical");
_content.componentTabIndex = 1;
_tabview.addComponent(_content);
}
if (_tabs == null) {
_tabs = new TabBar();
_tabs.id = "tabview-tabs";
_tabs.addClass("tabview-tabs");
_tabs.componentTabIndex = 0;
_tabview.addComponent(_tabs);
}
}
private override function get_numComponents():Null<Int> {
return _views.length;
}
public override function addComponent(child:Component):Component {
if (child != _content && child != _tabs) {
var text:String = child.text;
var icon:String = null;
if ((child is Box)) {
icon = cast(child, Box).icon;
}
child.parentComponent = _content; // this may be ill-conceived: the child wont many not be added to the _contents yet, which means it has no parentComponent
// it will, when the page changes, be set - but until then we can set it so things like "findAncestor" will work as expected
child.registerEvent(UIEvent.PROPERTY_CHANGE, onPagePropertyChanged);
_views.push(child);
var button:Button = new Button();
if (child.disabled == true) {
button.disabled = true;
}
button.text = text;
button.icon = icon;
button.tooltip = child.tooltip;
if (child.id != null) {
button.id = child.id + "TabButton";
}
_tabs.addComponent(button);
return child;
}
return null;
}
public override function addComponentAt(child:Component, index:Int):Component {
if (child != _content && child != _tabs) {
var text:String = child.text;
var icon:String = null;
if ((child is Box)) {
icon = cast(child, Box).icon;
}
child.parentComponent = _content; // this may be ill-conceived: the child wont many not be added to the _contents yet, which means it has no parentComponent
// it will, when the page changes, be set - but until then we can set it so things like "findAncestor" will work as expected
child.registerEvent(UIEvent.PROPERTY_CHANGE, onPagePropertyChanged);
_views.insert(index, child);
var button:Button = new Button();
button.text = text;
button.icon = icon;
button.tooltip = child.tooltip;
if (child.id != null) {
button.id = child.id + "_button";
}
_tabs.addComponentAt(button, index);
return child;
}
return null;
}
private function onPagePropertyChanged(event:UIEvent) {
if (event.data == "text") {
var index = _views.indexOf(event.target);
var button = _tabs.getTab(index);
if (button != null && button.text != event.target.text) {
button.text = event.target.text;
}
} else if (event.data == "icon") {
var index = _views.indexOf(event.target);
var button = cast(_tabs.getTab(index), Button);
if (button != null && button.icon != cast(event.target, Box).icon) {
button.icon = cast(event.target, Box).icon;
}
} else if (event.data == "disabled") {
var index = _views.indexOf(event.target);
var button = cast(_tabs.getTab(index), Button);
if (button != null && button.disabled != cast(event.target, Box).disabled) {
button.disabled = cast(event.target, Box).disabled;
}
}
}
public override function removeComponent(child:Component, dispose:Bool = true, invalidate:Bool = true):Component {
if (child != _content && child != _tabs) {
switch _views.indexOf(child) {
case -1:
case i:
_tabs.pauseEvent(UIEvent.CLOSE);
_tabs.pauseEvent(UIEvent.BEFORE_CLOSE);
if (_currentView == child) {
_currentView = null;
}
child.unregisterEvent(UIEvent.PROPERTY_CHANGE, onPagePropertyChanged);
_views.remove(child);
if (_content.getComponentIndex(child) != -1) {
_content.removeComponent(child, dispose, invalidate);
}
_tabs.removeTab(i);
_tabs.resumeEvent(UIEvent.CLOSE);
_tabs.resumeEvent(UIEvent.BEFORE_CLOSE);
return child;
}
}
return null;
}
public override function removeComponentAt(index:Int, dispose:Bool = true, invalidate:Bool = true):Component {
return _tabs.removeComponentAt(index, dispose, invalidate);
}
public override function getComponentIndex(child:Component):Int {
return _views.indexOf(child);
}
public override function setComponentIndex(child:Component, index:Int):Component {
if (child != _content && child != _tabs) {
switch _views.indexOf(child) {
case -1:
case i:
_views.splice(i, 1);
_views.insert(index, child);
_tabs.setComponentIndex(_tabs.getComponentAt(i), index);
return child;
}
}
return null;
}
public override function getComponentAt(index:Int):Component {
return _views[index];
}
public override function findComponent<T:Component>(criteria:String, type:Class<T>, recursive:Null<Bool>, searchType:String):Null<T> {
var match = super.findComponent(criteria, type, recursive, searchType);
if (match == null) {
for (view in _views) {
match = view.findComponent(criteria, type, recursive, searchType);
if (view.matchesSearch(criteria, type, searchType)) {
return cast view;
} else {
match = view.findComponent(criteria, type, recursive, searchType);
}
if (match != null) {
break;
}
}
}
return cast match;
}
public override function applyStyle(style:Style) {
super.applyStyle(style);
haxe.ui.macros.ComponentMacros.cascadeStylesTo("tabview-tabs", [
color, fontName, fontSize, cursor, textAlign, fontBold, fontUnderline, fontItalic
], false);
}
}