Updated TabsLayout

This commit is contained in:
anoshenko 2021-11-17 12:32:37 +03:00
parent f74fede3db
commit 07e1dd3799
14 changed files with 943 additions and 171 deletions

View File

@ -303,6 +303,12 @@ function tabClickEvent(layoutId, tabNumber, event) {
sendMessage("tabClick{session=" + sessionID + ",id=" + layoutId + ",number=" + tabNumber + "}"); sendMessage("tabClick{session=" + sessionID + ",id=" + layoutId + ",number=" + tabNumber + "}");
} }
function tabCloseClickEvent(layoutId, tabNumber, event) {
event.stopPropagation();
event.preventDefault();
sendMessage("tabCloseClick{session=" + sessionID + ",id=" + layoutId + ",number=" + tabNumber + "}");
}
function tabKeyClickEvent(layoutId, tabNumber, event) { function tabKeyClickEvent(layoutId, tabNumber, event) {
if (enterOrSpaceKeyClickEvent(event)) { if (enterOrSpaceKeyClickEvent(event)) {
tabClickEvent(layoutId, tabNumber, event) tabClickEvent(layoutId, tabNumber, event)

View File

@ -33,6 +33,12 @@ theme {
ruiButtonDisabledTextColor = #FFA0A0A0, ruiButtonDisabledTextColor = #FFA0A0A0,
ruiHighlightColor = #FF1A74E8, ruiHighlightColor = #FF1A74E8,
ruiHighlightTextColor = #FFFFFFFF, ruiHighlightTextColor = #FFFFFFFF,
ruiTabsBackgroundColor = #FF303030,
ruiInactiveTabColor = #FF606060,
ruiInactiveTabTextColor = #FFE0E0E0,
ruiActiveTabColor = #FF000000,
ruiActiveTabTextColor = #FFFFFFFF,
}, },
constants = _{ constants = _{
ruiButtonHorizontalPadding = 16px, ruiButtonHorizontalPadding = 16px,
@ -49,7 +55,8 @@ theme {
ruiPopupButtonGap = 4px, ruiPopupButtonGap = 4px,
ruiTabSpace = 2px, ruiTabSpace = 2px,
ruiTabHeight = 32px, ruiTabHeight = 32px,
ruiTabPadding = 2px, ruiTabsPadding = 2px,
ruiTabRadius = 2px,
}, },
constants:touch = _{ constants:touch = _{
ruiButtonHorizontalPadding = 20px, ruiButtonHorizontalPadding = 20px,
@ -114,26 +121,42 @@ theme {
ruiActiveTab { ruiActiveTab {
background-color = @ruiActiveTabColor, background-color = @ruiActiveTabColor,
text-color = @ruiActiveTabTextColor, text-color = @ruiActiveTabTextColor,
padding-left = 8px, padding-left = 4px,
padding-right = 8px, padding-right = 4px,
radius = @ruiTabRadius,
}, },
ruiInactiveTab { ruiInactiveTab {
background-color = @ruiInactiveTabColor, background-color = @ruiInactiveTabColor,
text-color = @ruiInactiveTabTextColor, text-color = @ruiInactiveTabTextColor,
padding-left = 8px, padding-left = 4px,
padding-right = 8px, padding-right = 4px,
radius = @ruiTabRadius,
}, },
ruiActiveVerticalTab { ruiActiveVerticalTab {
background-color = @ruiActiveTabColor, background-color = @ruiActiveTabColor,
text-color = @ruiActiveTabTextColor, text-color = @ruiActiveTabTextColor,
padding-top = 8px, padding-top = 4px,
padding-bottom = 8px, padding-bottom = 4px,
radius = @ruiTabRadius,
}, },
ruiInactiveVerticalTab { ruiInactiveVerticalTab {
background-color = @ruiInactiveTabColor, background-color = @ruiInactiveTabColor,
text-color = @ruiInactiveTabTextColor, text-color = @ruiInactiveTabTextColor,
padding-top = 8px, padding-top = 4px,
padding-bottom = 8px, padding-bottom = 4px,
radius = @ruiTabRadius,
},
ruiTabCloseButton {
width = 16px,
height = 16px,
cell-vertical-align = center,
cell-horizontal-align = center,
font = Helvetica,
text-size = 16px,
},
ruiTabCloseButton:hover {
background-color = @ruiTabsBackgroundColor,
radius = 3px,
}, },
ruiPopup { ruiPopup {
background-color = @ruiPopupBackgroundColor, background-color = @ruiPopupBackgroundColor,

View File

@ -16,7 +16,7 @@ GridLayout {
content = [ content = [
GridLayout { GridLayout {
id = rootTitle, width = 100%, cell-width = "auto, 1fr", id = rootTitle, width = 100%, cell-width = "auto, 1fr",
cell-vertical-align = center, background-color = #ffc0ded9, cell-vertical-align = center, background-color = #ffc0ded9, text-color = black,
content = [ content = [
ImageView { ImageView {
id = rootTitleButton, padding = 8px, src = menu_icon.svg, id = rootTitleButton, padding = 8px, src = menu_icon.svg,
@ -78,6 +78,7 @@ func createDemo(session rui.Session) rui.SessionContent {
{"GridLayout", createGridLayoutDemo, nil}, {"GridLayout", createGridLayoutDemo, nil},
{"ColumnLayout", createColumnLayoutDemo, nil}, {"ColumnLayout", createColumnLayoutDemo, nil},
{"StackLayout", createStackLayoutDemo, nil}, {"StackLayout", createStackLayoutDemo, nil},
{"Tabs", createTabsDemo, nil},
{"Resizable", createResizableDemo, nil}, {"Resizable", createResizableDemo, nil},
{"ListView", createListViewDemo, nil}, {"ListView", createListViewDemo, nil},
{"Checkbox", createCheckboxDemo, nil}, {"Checkbox", createCheckboxDemo, nil},
@ -99,7 +100,6 @@ func createDemo(session rui.Session) rui.SessionContent {
{"Mouse events", createMouseEventsDemo, nil}, {"Mouse events", createMouseEventsDemo, nil},
{"Pointer events", createPointerEventsDemo, nil}, {"Pointer events", createPointerEventsDemo, nil},
{"Touch events", createTouchEventsDemo, nil}, {"Touch events", createTouchEventsDemo, nil},
//{"Tabs", createTabsDemo, nil},
} }
return sessionContent return sessionContent

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
sodipodi:docname="black_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:document-units="px"
showgrid="true"
units="px"
inkscape:showpageshadow="false"
inkscape:zoom="24"
inkscape:cx="7.1458333"
inkscape:cy="8"
inkscape:window-width="1920"
inkscape:window-height="968"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid846" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Слой 1"
inkscape:groupmode="layer"
id="layer1">
<circle
style="fill:#000000;stroke:#ffffff;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path848"
cx="8"
cy="8"
r="7.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
sodipodi:docname="blue_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:document-units="px"
showgrid="true"
units="px"
inkscape:showpageshadow="false"
inkscape:zoom="24"
inkscape:cx="7.1458333"
inkscape:cy="8"
inkscape:window-width="1920"
inkscape:window-height="968"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid846" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Слой 1"
inkscape:groupmode="layer"
id="layer1">
<circle
style="fill:#0000ff;stroke:none"
id="path848"
cx="8"
cy="8"
r="8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
sodipodi:docname="green_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:document-units="px"
showgrid="true"
units="px"
inkscape:showpageshadow="false"
inkscape:zoom="24"
inkscape:cx="7.1458333"
inkscape:cy="8"
inkscape:window-width="1920"
inkscape:window-height="968"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid846" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Слой 1"
inkscape:groupmode="layer"
id="layer1">
<circle
style="fill:#00ff00;stroke:none"
id="path848"
cx="8"
cy="8"
r="8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="16"
height="16"
viewBox="0 0 16 16"
version="1.1"
id="svg5"
inkscape:version="1.1 (c4e8f9e, 2021-05-24)"
sodipodi:docname="red_icon.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:document-units="px"
showgrid="true"
units="px"
inkscape:showpageshadow="false"
inkscape:zoom="24"
inkscape:cx="7.1458333"
inkscape:cy="8"
inkscape:window-width="1920"
inkscape:window-height="968"
inkscape:window-x="0"
inkscape:window-y="25"
inkscape:window-maximized="1"
inkscape:current-layer="layer1">
<inkscape:grid
type="xygrid"
id="grid846" />
</sodipodi:namedview>
<defs
id="defs2" />
<g
inkscape:label="Слой 1"
inkscape:groupmode="layer"
id="layer1">
<circle
style="fill:#ff0000;stroke:none"
id="path848"
cx="8"
cy="8"
r="8" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1,6 +1,8 @@
package main package main
import ( import (
"fmt"
"github.com/anoshenko/rui" "github.com/anoshenko/rui"
) )
@ -8,12 +10,12 @@ const tabsDemoText = `
GridLayout { GridLayout {
style = demoPage, style = demoPage,
content = [ content = [
TabsLayout { id = tabsLayout, width = 100%, height = 100%, tabs = top, TabsLayout { id = tabsLayout, width = 100%, height = 100%, tabs = top, tab-close-button = true,
content = [ content = [
View { width = 300px, height = 200px, background-color = #FFFF0000, title = "Red tab"}, View { width = 300px, height = 200px, background-color = #FFFF0000, title = "Red tab", icon = red_icon.svg },
View { width = 400px, height = 250px, background-color = #FF00FF00, title = "Green tab"}, View { width = 400px, height = 250px, background-color = #FF00FF00, title = "Green tab", icon = green_icon.svg },
View { width = 100px, height = 400px, background-color = #FF0000FF, title = "Blue tab"}, View { width = 100px, height = 400px, background-color = #FF0000FF, title = "Blue tab", icon = blue_icon.svg },
View { width = 300px, height = 200px, background-color = #FF000000, title = "Black tab"}, View { width = 300px, height = 200px, background-color = #FF000000, title = "Black tab", icon = black_icon.svg },
] ]
}, },
ListLayout { ListLayout {
@ -23,8 +25,8 @@ GridLayout {
style = optionsTable, style = optionsTable,
content = [ content = [
TextView { row = 0, text = "Tabs location" }, TextView { row = 0, text = "Tabs location" },
DropDownList { row = 0, column = 1, id = tabsTypeList, current = 1, DropDownList { row = 0, column = 1, id = tabsTypeList,
items = ["hidden", "top", "bottom", "left", "right", "left list", "right list"] items = ["top", "bottom", "left", "right", "left list", "right list", "hidden"]
} }
] ]
} }
@ -44,5 +46,8 @@ func createTabsDemo(session rui.Session) rui.View {
rui.Set(view, "tabsLayout", rui.Tabs, number) rui.Set(view, "tabsLayout", rui.Tabs, number)
}) })
rui.Set(view, "tabsLayout", rui.TabCloseEvent, func(index int) {
rui.ShowMessage("", fmt.Sprintf(`The close button of the tab "%d" was clicked`, index), session)
})
return view return view
} }

49
params.go Normal file
View File

@ -0,0 +1,49 @@
package rui
import "sort"
// Params defines a type of a parameters list
type Params map[string]interface{}
func (params Params) Get(tag string) interface{} {
return params.getRaw(tag)
}
func (params Params) getRaw(tag string) interface{} {
if value, ok := params[tag]; ok {
return value
}
return nil
}
func (params Params) Set(tag string, value interface{}) bool {
params.setRaw(tag, value)
return true
}
func (params Params) setRaw(tag string, value interface{}) {
if value != nil {
params[tag] = value
} else {
delete(params, tag)
}
}
func (params Params) Remove(tag string) {
delete(params, tag)
}
func (params Params) Clear() {
for tag := range params {
delete(params, tag)
}
}
func (params Params) AllTags() []string {
tags := make([]string, 0, len(params))
for t := range params {
tags = append(tags, t)
}
sort.Strings(tags)
return tags
}

View File

@ -13,6 +13,7 @@ const (
OutsideClose = "outside-close" OutsideClose = "outside-close"
Buttons = "buttons" Buttons = "buttons"
ButtonsAlign = "buttons-align" ButtonsAlign = "buttons-align"
DismissEvent = "dismiss-event"
) )
type PopupButton struct { type PopupButton struct {
@ -32,9 +33,9 @@ type Popup interface {
} }
type popupData struct { type popupData struct {
//propertyList layerView View
layerView View view View
view View dismissListener []func(Popup)
} }
type popupManager struct { type popupManager struct {
@ -43,17 +44,61 @@ type popupManager struct {
func (popup *popupData) init(view View, params Params) { func (popup *popupData) init(view View, params Params) {
popup.view = view popup.view = view
props := propertyList{properties: params}
session := view.Session() session := view.Session()
popup.dismissListener = []func(Popup){}
if value, ok := params[DismissEvent]; ok && value != nil {
switch value := value.(type) {
case func(Popup):
popup.dismissListener = []func(Popup){value}
case func():
popup.dismissListener = []func(Popup){
func(popup Popup) {
value()
},
}
case []func(Popup):
for _, fn := range value {
if fn != nil {
popup.dismissListener = append(popup.dismissListener, fn)
}
}
case []func():
for _, fn := range value {
if fn != nil {
popup.dismissListener = append(popup.dismissListener, func(popup Popup) {
fn()
})
}
}
case []interface{}:
for _, val := range value {
if val != nil {
switch fn := val.(type) {
case func(Popup):
popup.dismissListener = append(popup.dismissListener, fn)
case func():
popup.dismissListener = append(popup.dismissListener, func(popup Popup) {
fn()
})
}
}
}
}
}
var title View = nil var title View = nil
titleStyle := "ruiPopupTitle" titleStyle := "ruiPopupTitle"
closeButton, _ := boolProperty(&props, CloseButton, session) closeButton, _ := boolProperty(params, CloseButton, session)
outsideClose, _ := boolProperty(&props, OutsideClose, session) outsideClose, _ := boolProperty(params, OutsideClose, session)
vAlign, _ := enumProperty(&props, VerticalAlign, session, CenterAlign) vAlign, _ := enumProperty(params, VerticalAlign, session, CenterAlign)
hAlign, _ := enumProperty(&props, HorizontalAlign, session, CenterAlign) hAlign, _ := enumProperty(params, HorizontalAlign, session, CenterAlign)
buttonsAlign, _ := enumProperty(&props, ButtonsAlign, session, RightAlign) buttonsAlign, _ := enumProperty(params, ButtonsAlign, session, RightAlign)
buttons := []PopupButton{} buttons := []PopupButton{}
if value, ok := params[Buttons]; ok && value != nil { if value, ok := params[Buttons]; ok && value != nil {
@ -110,7 +155,7 @@ func (popup *popupData) init(view View, params Params) {
viewRow := 0 viewRow := 0
if title != nil || closeButton { if title != nil || closeButton {
viewRow = 1 viewRow = 1
titleHeight, _ := sizeConstant(popup.Session(), "popupTitleHeight") titleHeight, _ := sizeConstant(popup.Session(), "ruiPopupTitleHeight")
titleView := NewGridLayout(session, Params{ titleView := NewGridLayout(session, Params{
Row: 0, Row: 0,
Style: titleStyle, Style: titleStyle,
@ -147,7 +192,7 @@ func (popup *popupData) init(view View, params Params) {
if buttonCount := len(buttons); buttonCount > 0 { if buttonCount := len(buttons); buttonCount > 0 {
cellHeight = append(cellHeight, AutoSize()) cellHeight = append(cellHeight, AutoSize())
gap, _ := sizeConstant(session, "popupButtonGap") gap, _ := sizeConstant(session, "ruiPopupButtonGap")
cellWidth := []SizeUnit{} cellWidth := []SizeUnit{}
for i := 0; i < buttonCount; i++ { for i := 0; i < buttonCount; i++ {
cellWidth = append(cellWidth, Fr(1)) cellWidth = append(cellWidth, Fr(1))
@ -212,6 +257,9 @@ func (popup *popupData) Session() Session {
func (popup *popupData) Dismiss() { func (popup *popupData) Dismiss() {
popup.Session().popupManager().dismissPopup(popup) popup.Session().popupManager().dismissPopup(popup)
for _, listener := range popup.dismissListener {
listener(popup)
}
// TODO // TODO
} }

View File

@ -104,7 +104,10 @@ type popupMenuData struct {
} }
func (popup *popupMenuData) itemClick(list ListView, n int) { func (popup *popupMenuData) itemClick(list ListView, n int) {
popup.popup.Dismiss() if popup.popup != nil {
popup.popup.Dismiss()
popup.popup = nil
}
if popup.result != nil { if popup.result != nil {
popup.result(n) popup.result(n)
} }
@ -128,11 +131,11 @@ func (popup *popupMenuData) IsListItemEnabled(index int) bool {
const PopupMenuResult = "popup-menu-result" const PopupMenuResult = "popup-menu-result"
// ShowMenu displays the popup with text message // ShowMenu displays the popup with text message
func ShowMenu(session Session, params Params) bool { func ShowMenu(session Session, params Params) Popup {
value, ok := params[Items] value, ok := params[Items]
if !ok || value == nil { if !ok || value == nil {
ErrorLog("Unable to show empty menu") ErrorLog("Unable to show empty menu")
return false return nil
} }
var adapter ListAdapter var adapter ListAdapter
@ -149,7 +152,7 @@ func ShowMenu(session Session, params Params) bool {
default: default:
notCompatibleType(Items, value) notCompatibleType(Items, value)
return false return nil
} }
value, ok = params[PopupMenuResult] value, ok = params[PopupMenuResult]
@ -167,5 +170,5 @@ func ShowMenu(session Session, params Params) bool {
data.popup = NewPopup(listView, params) data.popup = NewPopup(listView, params)
data.popup.Show() data.popup.Show()
FocusView(listView) FocusView(listView)
return true return data.popup
} }

View File

@ -56,6 +56,7 @@ var boolProperties = []string{
Muted, Muted,
AnimationPaused, AnimationPaused,
Multiple, Multiple,
TabCloseButton,
} }
var intProperties = []string{ var intProperties = []string{
@ -251,9 +252,9 @@ var enumProperties = map[string]struct {
[]string{"none", "solid", "dashed", "dotted", "double"}, []string{"none", "solid", "dashed", "dotted", "double"},
}, },
Tabs: { Tabs: {
[]string{"hidden", "top", "bottom", "left", "right", "left-list", "right-list"}, []string{"top", "bottom", "left", "right", "left-list", "right-list", "hidden"},
"", "",
[]string{"hidden", "top", "bottom", "left", "right", "left-list", "right-list"}, []string{"top", "bottom", "left", "right", "left-list", "right-list", "hidden"},
}, },
NumberPickerType: { NumberPickerType: {
[]string{"editor", "slider"}, []string{"editor", "slider"},

View File

@ -7,41 +7,49 @@ import (
) )
const ( const (
// HiddenTabs - tabs of TabsLayout are hidden // CurrentTabChangedEvent is the constant for "current-tab-changed" property tag.
HiddenTabs = 0 // The "current-tab-changed" event occurs when the new tab becomes active.
// The main listener format: func(TabsLayout, int, int), where
// the second argument is the index of the new active tab,
// the third argument is the index of the old active tab.
CurrentTabChangedEvent = "current-tab-changed"
// Icon is the constant for "icon" property tag.
// The string "icon" property defines the icon name that is displayed in the tab.
Icon = "icon"
// TabCloseButton is the constant for "tab-close-button" property tag.
// The "tab-close-button" is the bool property. If it is "true" then a close button is displayed within the tab.
TabCloseButton = "tab-close-button"
// TabCloseEvent is the constant for "tab-close-event" property tag.
// The "tab-close-event" occurs when when the user clicks on the tab close button.
// The main listener format: func(TabsLayout, int), where the second argument is the index of the tab.
TabCloseEvent = "tab-close-event"
// TopTabs - tabs of TabsLayout are on the top // TopTabs - tabs of TabsLayout are on the top
TopTabs = 1 TopTabs = 0
// BottomTabs - tabs of TabsLayout are on the bottom // BottomTabs - tabs of TabsLayout are on the bottom
BottomTabs = 2 BottomTabs = 1
// LeftTabs - tabs of TabsLayout are on the left // LeftTabs - tabs of TabsLayout are on the left. Bookmarks are rotated counterclockwise 90 degrees.
LeftTabs = 3 LeftTabs = 2
// RightTabs - tabs of TabsLayout are on the right // RightTabs - tabs of TabsLayout are on the right. Bookmarks are rotated clockwise 90 degrees.
RightTabs = 4 RightTabs = 3
// LeftListTabs - tabs of TabsLayout are on the left // LeftListTabs - tabs of TabsLayout are on the left
LeftListTabs = 5 LeftListTabs = 4
// RightListTabs - tabs of TabsLayout are on the right // RightListTabs - tabs of TabsLayout are on the right
RightListTabs = 6 RightListTabs = 5
// HiddenTabs - tabs of TabsLayout are hidden
HiddenTabs = 6
inactiveTabStyle = "data-inactiveTabStyle"
activeTabStyle = "data-activeTabStyle"
) )
// TabsLayoutCurrentChangedListener - listener of the current tab changing
type TabsLayoutCurrentChangedListener interface {
OnTabsLayoutCurrentChanged(tabsLayout TabsLayout, newCurrent int, newCurrentView View, oldCurrent int, oldCurrentView View)
}
type tabsLayoutCurrentChangedListenerFunc struct {
listenerFunc func(tabsLayout TabsLayout, newCurrent int, newCurrentView View, oldCurrent int, oldCurrentView View)
}
func (listener *tabsLayoutCurrentChangedListenerFunc) OnTabsLayoutCurrentChanged(tabsLayout TabsLayout,
newCurrent int, newCurrentView View, oldCurrent int, oldCurrentView View) {
if listener.listenerFunc != nil {
listener.listenerFunc(tabsLayout, newCurrent, newCurrentView, oldCurrent, oldCurrentView)
}
}
// TabsLayout - multi-tab container of View // TabsLayout - multi-tab container of View
type TabsLayout interface { type TabsLayout interface {
ViewsContainer ViewsContainer
ListAdapter
/* /*
// Current return the index of active tab // Current return the index of active tab
currentItem() int currentItem() int
@ -57,18 +65,14 @@ type TabsLayout interface {
TabStyle() (string, string) TabStyle() (string, string)
SetTabStyle(tabStyle string, activeTabStyle string) SetTabStyle(tabStyle string, activeTabStyle string)
*/ */
// SetCurrentTabChangedListener add the listener of the current tab changing
SetCurrentTabChangedListener(listener TabsLayoutCurrentChangedListener)
// SetCurrentTabChangedListener add the listener function of the current tab changing
SetCurrentTabChangedListenerFunc(listenerFunc func(tabsLayout TabsLayout,
newCurrent int, newCurrentView View, oldCurrent int, oldCurrentView View))
} }
type tabsLayoutData struct { type tabsLayoutData struct {
viewsContainerData viewsContainerData
//currentTab, tabsLocation int //currentTab, tabsLocation int
//tabStyle, activeTabStyle string //tabStyle, activeTabStyle string
tabListener TabsLayoutCurrentChangedListener tabListener []func(TabsLayout, int, int)
tabCloseListener []func(TabsLayout, int)
} }
// NewTabsLayout create new TabsLayout object and return it // NewTabsLayout create new TabsLayout object and return it
@ -87,7 +91,8 @@ func (tabsLayout *tabsLayoutData) Init(session Session) {
tabsLayout.viewsContainerData.Init(session) tabsLayout.viewsContainerData.Init(session)
tabsLayout.tag = "TabsLayout" tabsLayout.tag = "TabsLayout"
tabsLayout.systemClass = "ruiTabsLayout" tabsLayout.systemClass = "ruiTabsLayout"
tabsLayout.tabListener = nil tabsLayout.tabListener = []func(TabsLayout, int, int){}
tabsLayout.tabCloseListener = []func(TabsLayout, int){}
} }
func (tabsLayout *tabsLayoutData) currentItem() int { func (tabsLayout *tabsLayoutData) currentItem() int {
@ -95,8 +100,101 @@ func (tabsLayout *tabsLayoutData) currentItem() int {
return result return result
} }
func (tabsLayout *tabsLayoutData) Set(tag string, value interface{}) bool { func (tabsLayout *tabsLayoutData) Get(tag string) interface{} {
return tabsLayout.get(strings.ToLower(tag))
}
func (tabsLayout *tabsLayoutData) get(tag string) interface{} {
switch tag { switch tag {
case CurrentTabChangedEvent:
return tabsLayout.tabListener
case TabCloseEvent:
return tabsLayout.tabCloseListener
}
return tabsLayout.viewsContainerData.get(tag)
}
func (tabsLayout *tabsLayoutData) Remove(tag string) {
tabsLayout.remove(strings.ToLower(tag))
}
func (tabsLayout *tabsLayoutData) remove(tag string) {
switch tag {
case CurrentTabChangedEvent:
tabsLayout.tabListener = []func(TabsLayout, int, int){}
case TabCloseEvent:
tabsLayout.tabCloseListener = []func(TabsLayout, int){}
case Current:
oldCurrent := tabsLayout.currentItem()
delete(tabsLayout.properties, Current)
if !tabsLayout.session.ignoreViewUpdates() && oldCurrent != 0 {
tabsLayout.session.runScript(fmt.Sprintf("activateTab(%v, %d);", tabsLayout.htmlID(), 0))
for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, 0, oldCurrent)
}
}
case Tabs:
delete(tabsLayout.properties, Tabs)
if !tabsLayout.session.ignoreViewUpdates() {
htmlID := tabsLayout.htmlID()
updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle(), tabsLayout.session)
updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle(), tabsLayout.session)
updateCSSStyle(htmlID, tabsLayout.session)
updateInnerHTML(htmlID, tabsLayout.session)
}
case TabStyle, CurrentTabStyle:
delete(tabsLayout.properties, tag)
if !tabsLayout.session.ignoreViewUpdates() {
htmlID := tabsLayout.htmlID()
updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle(), tabsLayout.session)
updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle(), tabsLayout.session)
updateInnerHTML(htmlID, tabsLayout.session)
}
case TabCloseButton:
delete(tabsLayout.properties, tag)
if !tabsLayout.session.ignoreViewUpdates() {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
}
default:
tabsLayout.viewsContainerData.remove(tag)
}
}
func (tabsLayout *tabsLayoutData) Set(tag string, value interface{}) bool {
return tabsLayout.set(strings.ToLower(tag), value)
}
func (tabsLayout *tabsLayoutData) set(tag string, value interface{}) bool {
if value == nil {
tabsLayout.remove(tag)
return true
}
switch tag {
case CurrentTabChangedEvent:
listeners := tabsLayout.valueToTabListeners(value)
if listeners == nil {
notCompatibleType(tag, value)
return false
}
tabsLayout.tabListener = listeners
case TabCloseEvent:
listeners := tabsLayout.valueToCloseListeners(value)
if listeners == nil {
notCompatibleType(tag, value)
return false
}
tabsLayout.tabCloseListener = listeners
case Current: case Current:
oldCurrent := tabsLayout.currentItem() oldCurrent := tabsLayout.currentItem()
if !tabsLayout.setIntProperty(Current, value) { if !tabsLayout.setIntProperty(Current, value) {
@ -107,10 +205,8 @@ func (tabsLayout *tabsLayoutData) Set(tag string, value interface{}) bool {
current := tabsLayout.currentItem() current := tabsLayout.currentItem()
if oldCurrent != current { if oldCurrent != current {
tabsLayout.session.runScript(fmt.Sprintf("activateTab(%v, %d);", tabsLayout.htmlID(), current)) tabsLayout.session.runScript(fmt.Sprintf("activateTab(%v, %d);", tabsLayout.htmlID(), current))
if tabsLayout.tabListener != nil { for _, listener := range tabsLayout.tabListener {
oldView := tabsLayout.views[oldCurrent] listener(tabsLayout, current, oldCurrent)
view := tabsLayout.views[current]
tabsLayout.tabListener.OnTabsLayoutCurrentChanged(tabsLayout, current, view, oldCurrent, oldView)
} }
} }
} }
@ -121,14 +217,14 @@ func (tabsLayout *tabsLayoutData) Set(tag string, value interface{}) bool {
} }
if !tabsLayout.session.ignoreViewUpdates() { if !tabsLayout.session.ignoreViewUpdates() {
htmlID := tabsLayout.htmlID() htmlID := tabsLayout.htmlID()
updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle(), tabsLayout.session)
updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle(), tabsLayout.session)
updateCSSStyle(htmlID, tabsLayout.session) updateCSSStyle(htmlID, tabsLayout.session)
updateInnerHTML(htmlID, tabsLayout.session) updateInnerHTML(htmlID, tabsLayout.session)
} }
case TabStyle, CurrentTabStyle: case TabStyle, CurrentTabStyle:
if value == nil { if text, ok := value.(string); ok {
delete(tabsLayout.properties, tag)
} else if text, ok := value.(string); ok {
if text == "" { if text == "" {
delete(tabsLayout.properties, tag) delete(tabsLayout.properties, tag)
} else { } else {
@ -141,18 +237,228 @@ func (tabsLayout *tabsLayoutData) Set(tag string, value interface{}) bool {
if !tabsLayout.session.ignoreViewUpdates() { if !tabsLayout.session.ignoreViewUpdates() {
htmlID := tabsLayout.htmlID() htmlID := tabsLayout.htmlID()
updateProperty(htmlID, "data-tabStyle", tabsLayout.inactiveTabStyle(), tabsLayout.session) updateProperty(htmlID, inactiveTabStyle, tabsLayout.inactiveTabStyle(), tabsLayout.session)
updateProperty(htmlID, "data-activeTabStyle", tabsLayout.activeTabStyle(), tabsLayout.session) updateProperty(htmlID, activeTabStyle, tabsLayout.activeTabStyle(), tabsLayout.session)
updateInnerHTML(htmlID, tabsLayout.session) updateInnerHTML(htmlID, tabsLayout.session)
} }
case TabCloseButton:
if !tabsLayout.setBoolProperty(tag, value) {
return false
}
if !tabsLayout.session.ignoreViewUpdates() {
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
}
default: default:
return tabsLayout.viewsContainerData.Set(tag, value) return tabsLayout.viewsContainerData.set(tag, value)
} }
return true return true
} }
func (tabsLayout *tabsLayoutData) valueToTabListeners(value interface{}) []func(TabsLayout, int, int) {
if value == nil {
return []func(TabsLayout, int, int){}
}
switch value := value.(type) {
case func(TabsLayout, int, int):
return []func(TabsLayout, int, int){value}
case func(TabsLayout, int):
fn := func(view TabsLayout, current, old int) {
value(view, current)
}
return []func(TabsLayout, int, int){fn}
case func(TabsLayout):
fn := func(view TabsLayout, current, old int) {
value(view)
}
return []func(TabsLayout, int, int){fn}
case func(int, int):
fn := func(view TabsLayout, current, old int) {
value(current, old)
}
return []func(TabsLayout, int, int){fn}
case func(int):
fn := func(view TabsLayout, current, old int) {
value(current)
}
return []func(TabsLayout, int, int){fn}
case func():
fn := func(view TabsLayout, current, old int) {
value()
}
return []func(TabsLayout, int, int){fn}
case []func(TabsLayout, int, int):
return value
case []func(TabsLayout, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, old int) {
val(view, current)
}
}
return listeners
case []func(TabsLayout):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, old int) {
val(view)
}
}
return listeners
case []func(int, int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, old int) {
val(current, old)
}
}
return listeners
case []func(int):
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, old int) {
val(current)
}
}
return listeners
case []func():
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, current, old int) {
val()
}
}
return listeners
case []interface{}:
listeners := make([]func(TabsLayout, int, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
switch val := val.(type) {
case func(TabsLayout, int, int):
listeners[i] = val
case func(TabsLayout, int):
listeners[i] = func(view TabsLayout, current, old int) {
val(view, current)
}
case func(TabsLayout):
listeners[i] = func(view TabsLayout, current, old int) {
val(view)
}
case func(int, int):
listeners[i] = func(view TabsLayout, current, old int) {
val(current, old)
}
case func(int):
listeners[i] = func(view TabsLayout, current, old int) {
val(current)
}
case func():
listeners[i] = func(view TabsLayout, current, old int) {
val()
}
default:
return nil
}
}
return listeners
}
return nil
}
func (tabsLayout *tabsLayoutData) valueToCloseListeners(value interface{}) []func(TabsLayout, int) {
if value == nil {
return []func(TabsLayout, int){}
}
switch value := value.(type) {
case func(TabsLayout, int):
return []func(TabsLayout, int){value}
case func(int):
fn := func(view TabsLayout, index int) {
value(index)
}
return []func(TabsLayout, int){fn}
case []func(TabsLayout, int):
return value
case []func(int):
listeners := make([]func(TabsLayout, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
listeners[i] = func(view TabsLayout, index int) {
val(index)
}
}
return listeners
case []interface{}:
listeners := make([]func(TabsLayout, int), len(value))
for i, val := range value {
if val == nil {
return nil
}
switch val := val.(type) {
case func(TabsLayout, int):
listeners[i] = val
case func(int):
listeners[i] = func(view TabsLayout, index int) {
val(index)
}
default:
return nil
}
}
return listeners
}
return nil
}
func (tabsLayout *tabsLayoutData) tabsLocation() int { func (tabsLayout *tabsLayoutData) tabsLocation() int {
tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0) tabs, _ := enumProperty(tabsLayout, Tabs, tabsLayout.session, 0)
return tabs return tabs
@ -180,31 +486,82 @@ func (tabsLayout *tabsLayoutData) activeTabStyle() string {
return "ruiActiveTab" return "ruiActiveTab"
} }
func (tabsLayout *tabsLayoutData) TabStyle() (string, string) { func (tabsLayout *tabsLayoutData) ListSize() int {
return tabsLayout.inactiveTabStyle(), tabsLayout.activeTabStyle() if tabsLayout.views == nil {
} tabsLayout.views = []View{}
func (tabsLayout *tabsLayoutData) SetCurrentTabChangedListener(listener TabsLayoutCurrentChangedListener) {
tabsLayout.tabListener = listener
}
/*
// SetCurrentTabChangedListener add the listener of the current tab changing
func (tabsLayout *tabsLayoutData) SetCurrentTabChangedListener(listener TabsLayoutCurrentChangedListener) {
tabsLayout.tabListener = listener
}
// SetCurrentTabChangedListener add the listener function of the current tab changing
func (tabsLayout *tabsLayoutData) SetCurrentTabChangedListenerFunc(listenerFunc func(tabsLayout TabsLayout,
newCurrent int, newCurrentView View, oldCurrent int, oldCurrentView View)) {
} }
*/ return len(tabsLayout.views)
}
func (tabsLayout *tabsLayoutData) SetCurrentTabChangedListenerFunc(listenerFunc func(tabsLayout TabsLayout, func (tabsLayout *tabsLayoutData) ListItem(index int, session Session) View {
newCurrent int, newCurrentView View, oldCurrent int, oldCurrentView View)) { if tabsLayout.views == nil {
listener := new(tabsLayoutCurrentChangedListenerFunc) tabsLayout.views = []View{}
listener.listenerFunc = listenerFunc }
tabsLayout.SetCurrentTabChangedListener(listener)
if index < 0 || index >= len(tabsLayout.views) {
return NewTextView(session, Params{
Text: "Invalid index",
})
}
views := []View{}
page := tabsLayout.views[index]
if icon, ok := stringProperty(page, Icon, session); ok && icon != "" {
views = append(views, NewImageView(session, Params{
Source: icon,
Row: 0,
Column: 0,
}))
}
title, ok := stringProperty(page, Title, session)
if !ok || title == "" {
title = "No title"
}
if !GetNotTranslate(tabsLayout, "") {
title, _ = tabsLayout.Session().GetString(title)
}
views = append(views, NewTextView(session, Params{
Text: title,
Row: 0,
Column: 1,
}))
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
if close, ok := boolProperty(page, TabCloseButton, tabsLayout.session); ok {
closeButton = close
}
if closeButton {
views = append(views, NewGridLayout(session, Params{
Style: "ruiTabCloseButton",
Row: 0,
Column: 2,
Content: "✕",
ClickEvent: func() {
// TODO close menu
for _, listener := range tabsLayout.tabCloseListener {
listener(tabsLayout, index)
}
},
}))
}
tabsHeight, _ := sizeConstant(session, "ruiTabHeight")
return NewGridLayout(session, Params{
Height: tabsHeight,
CellWidth: []SizeUnit{AutoSize(), Fr(1), AutoSize()},
CellVerticalAlign: CenterAlign,
GridRowGap: Px(8),
Content: views,
})
}
func (tabsLayout *tabsLayoutData) IsListItemEnabled(index int) bool {
return true
} }
// Append appends view to the end of view list // Append appends view to the end of view list
@ -212,14 +569,16 @@ func (tabsLayout *tabsLayoutData) Append(view View) {
if tabsLayout.views == nil { if tabsLayout.views == nil {
tabsLayout.views = []View{} tabsLayout.views = []View{}
} }
tabsLayout.viewsContainerData.Append(view) if view != nil {
if len(tabsLayout.views) == 1 { tabsLayout.viewsContainerData.Append(view)
tabsLayout.setIntProperty(Current, 0) if len(tabsLayout.views) == 1 {
if tabsLayout.tabListener != nil { tabsLayout.setIntProperty(Current, 0)
tabsLayout.tabListener.OnTabsLayoutCurrentChanged(tabsLayout, 0, tabsLayout.views[0], -1, nil) for _, listener := range tabsLayout.tabListener {
listener(tabsLayout, 0, -1)
}
} }
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
} }
updateInnerHTML(tabsLayout.htmlID(), tabsLayout.session)
} }
// Insert inserts view to the "index" position in view list // Insert inserts view to the "index" position in view list
@ -227,10 +586,12 @@ func (tabsLayout *tabsLayoutData) Insert(view View, index uint) {
if tabsLayout.views == nil { if tabsLayout.views == nil {
tabsLayout.views = []View{} tabsLayout.views = []View{}
} }
tabsLayout.viewsContainerData.Insert(view, index) if view != nil {
current := tabsLayout.currentItem() tabsLayout.viewsContainerData.Insert(view, index)
if current >= int(index) { current := tabsLayout.currentItem()
tabsLayout.Set(Current, current+1) if current >= int(index) {
tabsLayout.Set(Current, current+1)
}
} }
} }
@ -249,8 +610,8 @@ func (tabsLayout *tabsLayoutData) RemoveView(index uint) View {
if count == 1 { if count == 1 {
view := tabsLayout.views[0] view := tabsLayout.views[0]
tabsLayout.views = []View{} tabsLayout.views = []View{}
if tabsLayout.tabListener != nil { for _, listener := range tabsLayout.tabListener {
tabsLayout.tabListener.OnTabsLayoutCurrentChanged(tabsLayout, 0, nil, 0, view) listener(tabsLayout, 0, 0)
} }
return view return view
} }
@ -259,10 +620,8 @@ func (tabsLayout *tabsLayoutData) RemoveView(index uint) View {
removeCurrent := (i == current) removeCurrent := (i == current)
if i < current || (removeCurrent && i == count-1) { if i < current || (removeCurrent && i == count-1) {
tabsLayout.properties[Current] = current - 1 tabsLayout.properties[Current] = current - 1
if tabsLayout.tabListener != nil { for _, listener := range tabsLayout.tabListener {
currentView := tabsLayout.views[current-1] listener(tabsLayout, current-1, current)
oldCurrentView := tabsLayout.views[current]
tabsLayout.tabListener.OnTabsLayoutCurrentChanged(tabsLayout, current-1, currentView, current, oldCurrentView)
} }
} }
@ -341,8 +700,17 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
rowLayout = true rowLayout = true
} }
switch location {
case LeftTabs, RightTabs:
if tabsHeight.Type != Auto {
buffer.WriteString(` width: `)
buffer.WriteString(tabsHeight.cssString(""))
buffer.WriteByte(';')
}
}
var tabsPadding Bounds var tabsPadding Bounds
if value, ok := tabsLayout.session.Constant("ruiTabPadding"); ok { if value, ok := tabsLayout.session.Constant("ruiTabsPadding"); ok {
if tabsPadding.parse(value, tabsLayout.session) { if tabsPadding.parse(value, tabsLayout.session) {
if !tabsPadding.allFieldsAuto() { if !tabsPadding.allFieldsAuto() {
buffer.WriteByte(' ') buffer.WriteByte(' ')
@ -354,7 +722,7 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
} }
} }
if tabsBackground, ok := tabsLayout.session.Color("tabsBackgroundColor"); ok { if tabsBackground, ok := tabsLayout.session.Color("ruiTabsBackgroundColor"); ok {
buffer.WriteString(` background-color: `) buffer.WriteString(` background-color: `)
buffer.WriteString(tabsBackground.cssString()) buffer.WriteString(tabsBackground.cssString())
buffer.WriteByte(';') buffer.WriteByte(';')
@ -366,9 +734,51 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
activeStyle := tabsLayout.activeTabStyle() activeStyle := tabsLayout.activeTabStyle()
notTranslate := GetNotTranslate(tabsLayout, "") notTranslate := GetNotTranslate(tabsLayout, "")
closeButton, _ := boolProperty(tabsLayout, TabCloseButton, tabsLayout.session)
tabMargin := ""
if tabsSpace.Type != Auto && tabsSpace.Value > 0 {
if rowLayout {
tabMargin = ` margin-right: ` + tabsSpace.cssString("") + ";"
} else {
tabMargin = ` margin-bottom: ` + tabsSpace.cssString("") + ";"
}
}
var tabStyle, titleDiv string
switch location {
case LeftTabs, RightTabs:
tabStyle = `display: grid; grid-template-rows: auto 1fr auto; align-items: center; justify-items: center; grid-row-gap: 8px;`
case LeftListTabs, RightListTabs:
tabStyle = `display: grid; grid-template-columns: auto 1fr auto; align-items: start; justify-items: center; grid-column-gap: 8px;`
default:
tabStyle = `display: grid; grid-template-columns: auto 1fr auto; align-items: center; justify-items: center; grid-column-gap: 8px;`
}
switch location {
case LeftListTabs, RightListTabs:
if tabsHeight.Type != Auto {
tabStyle += ` height: ` + tabsHeight.cssString("") + ";"
}
}
switch location {
case LeftTabs:
titleDiv = `<div style="writing-mode: vertical-lr; transform: rotate(180deg); grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
case RightTabs:
titleDiv = `<div style="writing-mode: vertical-lr; grid-row-start: 2; grid-row-end: 3; grid-column-start: 1; grid-column-end: 2;">`
default:
titleDiv = `<div style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 2; grid-column-end: 3;">`
}
last := len(tabsLayout.views) - 1 last := len(tabsLayout.views) - 1
for n, view := range tabsLayout.views { for n, view := range tabsLayout.views {
title, _ := stringProperty(view, "title", tabsLayout.session) icon, _ := stringProperty(view, Icon, tabsLayout.session)
title, _ := stringProperty(view, Title, tabsLayout.session)
if !notTranslate { if !notTranslate {
title, _ = tabsLayout.Session().GetString(title) title, _ = tabsLayout.Session().GetString(title)
} }
@ -387,54 +797,70 @@ func (tabsLayout *tabsLayoutData) htmlSubviews(self View, buffer *strings.Builde
buffer.WriteString(tabsLayoutID) buffer.WriteString(tabsLayoutID)
buffer.WriteString(`\', `) buffer.WriteString(`\', `)
buffer.WriteString(strconv.Itoa(n)) buffer.WriteString(strconv.Itoa(n))
buffer.WriteString(`, event)`) buffer.WriteString(`, event)" onclick="tabKeyClickEvent(\'`)
buffer.WriteString(`" onclick="tabKeyClickEvent(\'`)
buffer.WriteString(tabsLayoutID) buffer.WriteString(tabsLayoutID)
buffer.WriteString(`\', `) buffer.WriteString(`\', `)
buffer.WriteString(strconv.Itoa(n)) buffer.WriteString(strconv.Itoa(n))
buffer.WriteString(`, event)" style="display: flex; flex-flow: row nowrap; justify-content: center; align-items: center; `) buffer.WriteString(`, event)" style="`)
buffer.WriteString(tabStyle)
if n != last && tabsSpace.Type != Auto && tabsSpace.Value > 0 { if n != last {
if rowLayout { buffer.WriteString(tabMargin)
buffer.WriteString(` margin-right: `)
buffer.WriteString(tabsSpace.cssString(""))
} else {
buffer.WriteString(` margin-bottom: `)
buffer.WriteString(tabsSpace.cssString(""))
}
buffer.WriteByte(';')
}
switch location {
case LeftListTabs, RightListTabs:
if tabsHeight.Type != Auto {
buffer.WriteString(` height: `)
buffer.WriteString(tabsHeight.cssString(""))
buffer.WriteByte(';')
}
} }
buffer.WriteString(`" data-container="`) buffer.WriteString(`" data-container="`)
buffer.WriteString(tabsLayoutID) buffer.WriteString(tabsLayoutID)
buffer.WriteString(`" data-view="`) buffer.WriteString(`" data-view="`)
//buffer.WriteString(view.htmlID())
buffer.WriteString(tabsLayoutID) buffer.WriteString(tabsLayoutID)
buffer.WriteString(`-page`) buffer.WriteString(`-page`)
buffer.WriteString(strconv.Itoa(n)) buffer.WriteString(strconv.Itoa(n))
buffer.WriteString(`"><div`) buffer.WriteString(`">`)
switch location { if icon != "" {
case LeftTabs: switch location {
buffer.WriteString(` style="writing-mode: vertical-lr; transform: rotate(180deg)">`) case LeftTabs:
buffer.WriteString(`<img style="grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;" src="`)
case RightTabs: case RightTabs:
buffer.WriteString(` style="writing-mode: vertical-lr;">`) buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
default: default:
buffer.WriteByte('>') buffer.WriteString(`<img style="grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;" src="`)
}
buffer.WriteString(icon)
buffer.WriteString(`">`)
} }
buffer.WriteString(titleDiv)
buffer.WriteString(title) buffer.WriteString(title)
buffer.WriteString(`</div></div>`) buffer.WriteString(`</div>`)
close, ok := boolProperty(view, TabCloseButton, tabsLayout.session)
if !ok {
close = closeButton
}
if close {
buffer.WriteString(`<div class="ruiTabCloseButton" onclick="tabCloseClickEvent(\'`)
buffer.WriteString(tabsLayoutID)
buffer.WriteString(`\', `)
buffer.WriteString(strconv.Itoa(n))
buffer.WriteString(`, event)" style="display: grid; `)
switch location {
case LeftTabs:
buffer.WriteString(`grid-row-start: 1; grid-row-end: 2; grid-column-start: 1; grid-column-end: 2;">`)
case RightTabs:
buffer.WriteString(`grid-row-start: 3; grid-row-end: 4; grid-column-start: 1; grid-column-end: 2;">`)
default:
buffer.WriteString(`grid-row-start: 1; grid-row-end: 2; grid-column-start: 3; grid-column-end: 4;">`)
}
buffer.WriteString(`✕</div>`)
}
buffer.WriteString(`</div>`)
} }
buffer.WriteString(`</div>`) buffer.WriteString(`</div>`)
@ -476,15 +902,23 @@ func (tabsLayout *tabsLayoutData) handleCommand(self View, command string, data
current := tabsLayout.currentItem() current := tabsLayout.currentItem()
if current != number { if current != number {
tabsLayout.properties[Current] = number tabsLayout.properties[Current] = number
if tabsLayout.tabListener != nil { for _, listener := range tabsLayout.tabListener {
oldView := tabsLayout.views[current] listener(tabsLayout, number, current)
view := tabsLayout.views[number]
tabsLayout.tabListener.OnTabsLayoutCurrentChanged(tabsLayout, number, view, current, oldView)
} }
} }
} }
} }
return true return true
case "tabCloseClick":
if numberText, ok := data.PropertyValue("number"); ok {
if number, err := strconv.Atoi(numberText); err == nil {
for _, listener := range tabsLayout.tabCloseListener {
listener(tabsLayout, number)
}
}
}
return true
} }
return tabsLayout.viewsContainerData.handleCommand(self, command, data) return tabsLayout.viewsContainerData.handleCommand(self, command, data)
} }

13
view.go
View File

@ -2,7 +2,6 @@ package rui
import ( import (
"fmt" "fmt"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@ -29,18 +28,6 @@ func (frame Frame) Bottom() float64 {
return frame.Top + frame.Height return frame.Top + frame.Height
} }
// Params defines a type of a parameters list
type Params map[string]interface{}
func (params Params) AllTags() []string {
tags := make([]string, 0, len(params))
for t := range params {
tags = append(tags, t)
}
sort.Strings(tags)
return tags
}
// View - base view interface // View - base view interface
type View interface { type View interface {
Properties Properties