From 381f872a4daab133ed53e85526281b6e29873007 Mon Sep 17 00:00:00 2001 From: Robert Jaros Date: Sat, 7 Oct 2017 02:19:47 +0200 Subject: New grid component (old renamed to ResponsiveGridPanel). Flex, HPanel, VPanel and DockPanel components. Cache for components attributes. --- src/main/assets/css/style.css | 33 --- src/main/kotlin/pl/treksoft/kvision/Showcase.kt | 68 +++-- .../kotlin/pl/treksoft/kvision/core/Container.kt | 20 +- src/main/kotlin/pl/treksoft/kvision/core/Root.kt | 10 +- src/main/kotlin/pl/treksoft/kvision/core/Widget.kt | 78 +++++- .../pl/treksoft/kvision/core/WidgetWrapper.kt | 27 +- src/main/kotlin/pl/treksoft/kvision/html/List.kt | 8 +- src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt | 5 - .../kotlin/pl/treksoft/kvision/panel/DockPanel.kt | 114 ++++++++ .../kotlin/pl/treksoft/kvision/panel/FlexPanel.kt | 157 +++++++++++ .../kotlin/pl/treksoft/kvision/panel/GridPanel.kt | 298 +++++++++++++-------- .../kotlin/pl/treksoft/kvision/panel/HPanel.kt | 34 +-- .../treksoft/kvision/panel/ResponsiveGridPanel.kt | 117 ++++++++ .../kotlin/pl/treksoft/kvision/panel/SplitPanel.kt | 2 +- .../kotlin/pl/treksoft/kvision/panel/StackPanel.kt | 8 +- .../kotlin/pl/treksoft/kvision/panel/TabPanel.kt | 4 +- .../kotlin/pl/treksoft/kvision/panel/VPanel.kt | 28 +- .../test/pl/treksoft/kvision/core/ContainerSpec.kt | 18 -- .../test/pl/treksoft/kvision/core/WidgetSpec.kt | 4 +- 19 files changed, 742 insertions(+), 291 deletions(-) create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/DockPanel.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt create mode 100644 src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt (limited to 'src') diff --git a/src/main/assets/css/style.css b/src/main/assets/css/style.css index 568f0edd..61696cb2 100644 --- a/src/main/assets/css/style.css +++ b/src/main/assets/css/style.css @@ -52,36 +52,3 @@ background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACEAAAAICAQAAADdTl4aAAAAIElEQVQoz2MwrTD9TxFsZ7jPcV+IIsjFQAUw6hFqegQA+xzRHT2p7pEAAAAASUVORK5CYII=') center center no-repeat #cecece; cursor: row-resize; } - -/* Dead Simple Grid (c) 2015 Vladimir Agafonkin */ - -.dsgrow .dsgrow { margin: 0 -1.5em; } -.dsgcol { padding: 0 1.5em; } -.dsgcolf { padding: 0 1.5em; } - -.dsgrow:after { - content: ""; - clear: both; - display: table; -} - -@media only screen { - -.dsgcol { - float: left; - width: 100%; - box-sizing: border-box; -} -.dsgcol::before { - content: "\200B"; -} - -.dsgcolf { - float: left; - box-sizing: border-box; -} -.dsgcolf::before { - content: "\200B"; -} - -} diff --git a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt index 04ce2615..3bc6a348 100644 --- a/src/main/kotlin/pl/treksoft/kvision/Showcase.kt +++ b/src/main/kotlin/pl/treksoft/kvision/Showcase.kt @@ -12,19 +12,14 @@ import pl.treksoft.kvision.html.TAG.H1 import pl.treksoft.kvision.modal.Alert import pl.treksoft.kvision.modal.Confirm import pl.treksoft.kvision.modal.Modal -import pl.treksoft.kvision.panel.DIRECTION -import pl.treksoft.kvision.panel.GRIDTYPE -import pl.treksoft.kvision.panel.GridPanel -import pl.treksoft.kvision.panel.HPanel -import pl.treksoft.kvision.panel.SplitPanel -import pl.treksoft.kvision.panel.TabPanel -import pl.treksoft.kvision.panel.VPanel +import pl.treksoft.kvision.panel.* import pl.treksoft.kvision.routing.routing class Showcase : ApplicationBase() { override fun start(state: Map) { val root = Root("showcase") + val container = Container(setOf("abc", "def")) val h1 = Tag(H1, "To jest test pisania tekstu", false, ALIGN.NONE, classes = setOf("test", "test2")) container.add(h1) @@ -74,7 +69,7 @@ class Showcase : ApplicationBase() { val tabs = TabPanel() tabs.addTab("Test zakładki", Label("test zakładki"), "fa-flag") tabs.addTab("Test zakładki2", Label("test zakładki2")) - tabs.addTab("Test zakładki3", tabs2, "fa-bars") +// tabs.addTab("Test zakładki3", tabs2, "fa-bars") val split = SplitPanel() split.add(tabs) @@ -115,31 +110,66 @@ class Showcase : ApplicationBase() { val img = Image(Img("kotlin.png"), "Image", true, IMAGESHAPE.ROUNDED) root.add(img) - val grid = GridPanel(gridtype = GRIDTYPE.DSG, align = ALIGN.RIGHT) + val grid = ResponsiveGridPanel(align = ALIGN.RIGHT) grid.add(Tag(DIV, "0,0"), 0, 0) grid.add(Tag(DIV, "1,1"), 1, 1) grid.add(Tag(DIV, "2,2"), 2, 2) - grid.add(Tag(DIV, "3,3"), 3, 3) root.add(grid) - val grid2 = GridPanel(align = ALIGN.CENTER) + val grid2 = ResponsiveGridPanel(align = ALIGN.CENTER) grid2.add(Tag(DIV, "0,0"), 0, 0, 8) grid2.add(Tag(DIV, "0,1"), 0, 1, 4) grid2.add(Tag(DIV, "1,1"), 1, 1, 8, 4) root.add(grid2) - val hPanel = HPanel(align = ALIGN.RIGHT) - hPanel.add(Label("1")) - hPanel.add(Label("2")) - hPanel.add(Label("3")) + val flexPanel = FlexPanel(FLEXDIR.ROW) + flexPanel.add(Label("1"), 3, 1) + flexPanel.add(Label("2"), 2, 2) + flexPanel.add(Label("3"), 1, 1) + flexPanel.add(tabs2, 4, 1) + root.add(flexPanel) + + val hPanel = HPanel(FLEXJUSTIFY.CENTER) + hPanel.add(Label("h1"), basis = 10) + hPanel.add(Label("h2"), basis = 20) + hPanel.add(Label("h3"), basis = 10) root.add(hPanel) - val vPanel = VPanel(align = ALIGN.CENTER) - vPanel.add(Label("1")) - vPanel.add(Label("2")) - vPanel.add(Label("3")) + val vPanel = VPanel(alignItems = FLEXALIGNITEMS.CENTER) + vPanel.add(Label("h1"), basis = 10) + vPanel.add(Label("h2"), basis = 20) + vPanel.add(Label("h3"), basis = 10) root.add(vPanel) + val grid3 = GridPanel(templateColumns = "1fr 1fr 1fr") + grid3.add(Label("hh1")) + grid3.add(Label("hh2")) + grid3.add(Label("hh3")) + grid3.add(Label("hh4")) + root.add(grid3) + + val grid4 = GridPanel(justifyItems = GRIDJUSTIFY.CENTER) + grid4.add(Label("hh1"), 1, 1) + grid4.add(Label("hh2"), 2, 2) + grid4.add(Label("hh3"), 3, 3) + grid4.add(Label("hh4"), 4, 4) + root.add(grid4) + + val dock = DockPanel() + dock.add(Label("left
left", rich = true), SIDE.LEFT) + dock.add(Label("right"), SIDE.RIGHT) + dock.add(Label("up"), SIDE.UP) + dock.add(Label("down"), SIDE.DOWN) + dock.add(Label("center"), SIDE.CENTER) +// root.add(dock) + + val pa = HPanel(alignItems = FLEXALIGNITEMS.FLEXEND) + pa.add(Label("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce nec fringilla turpis, vel molestie dolor. Vestibulum ut ex eget orci porta gravida eu sit amet tortor. Suspendisse vel fermentum purus, vel ornare tellus. Vivamus dictum, risus non viverra venenatis, magna mi pharetra purus, nec dignissim risus tortor a sem. Donec tincidunt dui ut eros laoreet consectetur. Nam dapibus vestibulum sem, eget accumsan ex vestibulum ac. Curabitur ac mi sit amet eros sodales dictum. Sed at felis at nunc aliquam finibus. Vestibulum lorem nulla, dictum ac libero non, mattis dictum nisl. Aenean semper lorem turpis. Praesent pellentesque ligula est, viverra molestie leo imperdiet ut. Nam vitae hendrerit justo. Nullam tincidunt et nibh ac volutpat. Aliquam vulputate mi aliquam fermentum rhoncus."),3) + pa.add(Image(Img("kotlin.png")),1) + pa.add(dock,2,alignSelf = FLEXALIGNITEMS.FLEXSTART) + dock.width = 400 + root.add(pa) + val modal = Modal("Test okienka") modal.add(Tag(TAG.H4, "ABC")) modal.add(Image(Img("kotlin.png"))) diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Container.kt b/src/main/kotlin/pl/treksoft/kvision/core/Container.kt index 575405c6..df854637 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Container.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Container.kt @@ -10,7 +10,7 @@ open class Container(classes: Set = setOf()) : Widget(classes) { } protected open fun childrenVNodes(): Array { - return children.filter { it.visible }.map { it.render() }.toTypedArray() + return children.filter { it.visible }.map { it.renderVNode() }.toTypedArray() } protected fun addInternal(child: Widget): Container { @@ -32,15 +32,10 @@ open class Container(classes: Set = setOf()) : Widget(classes) { } open fun remove(child: Widget): Container { - children.remove(child) - child.clearParent() - refresh() - return this - } - - open fun removeAt(index: Int): Container { - children.removeAt(index).clearParent() - refresh() + if (children.remove(child)) { + child.clearParent() + refresh() + } return this } @@ -54,4 +49,9 @@ open class Container(classes: Set = setOf()) : Widget(classes) { open fun getChildren(): List { return ArrayList(children) } + + override fun dispose() { + children.forEach { it.dispose() } + removeAll() + } } diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Root.kt b/src/main/kotlin/pl/treksoft/kvision/core/Root.kt index 52424f0e..0c8a80f6 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Root.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Root.kt @@ -6,10 +6,10 @@ import pl.treksoft.kvision.snabbdom.StringBoolPair class Root(id: String, private val fixed: Boolean = false) : Container() { private val modals: MutableList = mutableListOf() - private var rootVnode: VNode = render() + private var rootVnode: VNode = renderVNode() init { - rootVnode = KVManager.patch(id, this.render()) + rootVnode = KVManager.patch(id, this.renderVNode()) this.id = id roots.add(this) } @@ -25,7 +25,7 @@ class Root(id: String, private val fixed: Boolean = false) : Container() { } private fun modalsVNodes(): Array { - return modals.filter { it.visible }.map { it.render() }.toTypedArray() + return modals.filter { it.visible }.map { it.renderVNode() }.toTypedArray() } override fun getSnClass(): List { @@ -33,8 +33,8 @@ class Root(id: String, private val fixed: Boolean = false) : Container() { return super.getSnClass() + (css to true) } - override fun refresh(): Widget { - rootVnode = KVManager.patch(rootVnode, render()) + internal fun reRender(): Root { + rootVnode = KVManager.patch(rootVnode, renderVNode()) return this } diff --git a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt index c934aff2..e2a4f09f 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/Widget.kt @@ -26,7 +26,7 @@ open class Widget(classes: Set = setOf()) : KVObject { var parent: Widget? = null internal set - var visible: Boolean = true + open var visible: Boolean = true set(value) { val oldField = field field = value @@ -65,7 +65,17 @@ open class Widget(classes: Set = setOf()) : KVObject { private var vnode: VNode? = null - internal open fun render(): VNode { + private var snAttrsCache: List? = null + private var snStyleCache: List? = null + private var snClassCache: List? = null + private var snOnCache: com.github.snabbdom.On? = null + private var snHooksCache: com.github.snabbdom.Hooks? = null + + internal open fun renderVNode(): VNode { + return render() + } + + protected open fun render(): VNode { return kvh("div") } @@ -77,16 +87,56 @@ open class Widget(classes: Set = setOf()) : KVObject { return h(s, getSnOpt(), children) } - protected open fun getSnOpt(): VNodeData { + private fun getSnOpt(): VNodeData { return snOpt { - attrs = snAttrs(getSnAttrs()) - style = snStyle(getSnStyle()) - `class` = snClasses(getSnClass()) - on = getSnOn() - hook = getSnHooks() + attrs = snAttrs(getSnAttrsInternal()) + style = snStyle(getSnStyleInternal()) + `class` = snClasses(getSnClassInternal()) + on = getSnOnInternal() + hook = getSnHooksInternal() } } + private fun getSnAttrsInternal(): List { + return snAttrsCache ?: { + val s = getSnAttrs() + snAttrsCache = s + s + }() + } + + private fun getSnStyleInternal(): List { + return snStyleCache ?: { + val s = getSnStyle() + snStyleCache = s + s + }() + } + + private fun getSnClassInternal(): List { + return snClassCache ?: { + val s = getSnClass() + snClassCache = s + s + }() + } + + private fun getSnOnInternal(): com.github.snabbdom.On? { + return snOnCache ?: { + val s = getSnOn() + snOnCache = s + s + }() + } + + private fun getSnHooksInternal(): com.github.snabbdom.Hooks? { + return snHooksCache ?: { + val s = getSnHooks() + snHooksCache = s + s + }() + } + protected open fun getSnStyle(): List { val snstyle = mutableListOf() if (width != null) { @@ -206,8 +256,13 @@ open class Widget(classes: Set = setOf()) : KVObject { return this } - protected open fun refresh(): Widget { - this.parent?.refresh() + protected fun refresh(): Widget { + snAttrsCache = null + snStyleCache = null + snClassCache = null + snOnCache = null + snHooksCache = null + getRoot()?.reRender() return this } @@ -240,4 +295,7 @@ open class Widget(classes: Set = setOf()) : KVObject { val event = org.w3c.dom.CustomEvent(type, eventInitDict) return this.getElement()?.dispatchEvent(event) } + + open fun dispose() { + } } diff --git a/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt b/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt index 0aa13e64..6f6c2f57 100644 --- a/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt +++ b/src/main/kotlin/pl/treksoft/kvision/core/WidgetWrapper.kt @@ -2,15 +2,28 @@ package pl.treksoft.kvision.core import com.github.snabbdom.VNode -open class WidgetWrapper(private var delegate: Widget, classes: Set = setOf()) : Widget(classes) { +open class WidgetWrapper(internal var delegate: Widget?, classes: Set = setOf()) : Widget(classes) { - override fun render(): VNode { - val children = if (delegate.visible) { - arrayOf(delegate.render()) - } else { - arrayOf() + override var visible + get() = delegate?.visible == true + set(value) { + delegate?.visible = value } - return kvh("div", children) + + init { + @Suppress("LeakingThis") + delegate?.parent = this + } + + override fun render(): VNode { + return delegate?.let { + kvh("div", arrayOf(it.renderVNode())) + } ?: kvh("div") + } + + override fun dispose() { + delegate?.clearParent() + delegate = null } } diff --git a/src/main/kotlin/pl/treksoft/kvision/html/List.kt b/src/main/kotlin/pl/treksoft/kvision/html/List.kt index c6298ddf..9bac3f41 100644 --- a/src/main/kotlin/pl/treksoft/kvision/html/List.kt +++ b/src/main/kotlin/pl/treksoft/kvision/html/List.kt @@ -52,16 +52,16 @@ open class ListTag(type: LIST, elements: List? = null, rich: Boolean = f val res = when (type) { LIST.UL, LIST.OL, LIST.UNSTYLED, LIST.INLINE -> childrenElements.map { v -> if (v is Tag && v.type == TAG.LI) { - v.render() + v.renderVNode() } else { - h("li", arrayOf(v.render())) + h("li", arrayOf(v.renderVNode())) } } LIST.DL, LIST.DL_HORIZ -> childrenElements.mapIndexed { index, v -> if (v is Tag && v.type == TAG.LI) { - v.render() + v.renderVNode() } else { - h(if (index % 2 == 0) "dt" else "dd", arrayOf(v.render())) + h(if (index % 2 == 0) "dt" else "dd", arrayOf(v.renderVNode())) } } } diff --git a/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt b/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt index 564a9f6f..69a83dfb 100644 --- a/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt +++ b/src/main/kotlin/pl/treksoft/kvision/modal/Modal.kt @@ -107,11 +107,6 @@ open class Modal(caption: String? = null, closeButton: Boolean = true, return this } - open fun removeButtonAt(index: Int): Modal { - footer.removeAt(index) - return this - } - open fun removeAllButtons(): Modal { footer.removeAll() return this diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/DockPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/DockPanel.kt new file mode 100644 index 00000000..6d86727c --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/DockPanel.kt @@ -0,0 +1,114 @@ +package pl.treksoft.kvision.panel + +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget + +enum class SIDE { + LEFT, + RIGHT, + CENTER, + UP, + DOWN +} + +open class DockPanel(classes: Set = setOf()) : Container(classes = classes) { + protected var left: Widget? = null + protected var center: Widget? = null + protected var right: Widget? = null + protected var up: Widget? = null + protected var down: Widget? = null + + protected val mainContainer = FlexPanel(direction = FLEXDIR.COLUMN, justify = FLEXJUSTIFY.SPACEBETWEEN, + alignItems = FLEXALIGNITEMS.STRETCH) + protected val subContainer = FlexPanel(justify = FLEXJUSTIFY.SPACEBETWEEN, alignItems = FLEXALIGNITEMS.CENTER) + + init { + this.addInternal(mainContainer) + mainContainer.add(subContainer, 2) + } + + @Suppress("MagicNumber") + open fun add(widget: Widget, position: SIDE): DockPanel { + when (position) { + SIDE.UP -> { + up?.let { mainContainer.remove(it) } + up = widget + mainContainer.add(widget, 1, alignSelf = FLEXALIGNITEMS.CENTER) + } + SIDE.CENTER -> { + center?.let { subContainer.remove(it) } + center = widget + subContainer.add(widget, 2) + } + SIDE.LEFT -> { + left?.let { subContainer.remove(it) } + left = widget + subContainer.add(widget, 1) + } + SIDE.RIGHT -> { + right?.let { subContainer.remove(it) } + right = widget + subContainer.add(widget, 3) + } + SIDE.DOWN -> { + down?.let { mainContainer.remove(it) } + down = widget + mainContainer.add(widget, 3, alignSelf = FLEXALIGNITEMS.CENTER) + } + } + return this + } + + override fun add(child: Widget): Container { + return this.add(child, SIDE.CENTER) + } + + override fun addAll(children: List): Container { + children.forEach { this.add(it) } + return this + } + + override fun remove(child: Widget): Container { + if (child == left) removeAt(SIDE.LEFT) + if (child == center) removeAt(SIDE.CENTER) + if (child == right) removeAt(SIDE.RIGHT) + if (child == up) removeAt(SIDE.UP) + if (child == down) removeAt(SIDE.DOWN) + return this + } + + open fun removeAt(position: SIDE): Container { + when (position) { + SIDE.UP -> { + up?.let { mainContainer.remove(it) } + up = null + } + SIDE.CENTER -> { + center?.let { subContainer.remove(it) } + center = null + } + SIDE.LEFT -> { + left?.let { subContainer.remove(it) } + left = null + } + SIDE.RIGHT -> { + right?.let { subContainer.remove(it) } + right = null + } + SIDE.DOWN -> { + down?.let { mainContainer.remove(it) } + down = null + } + } + return this + } + + override fun removeAll(): Container { + removeAt(SIDE.LEFT) + removeAt(SIDE.CENTER) + removeAt(SIDE.RIGHT) + removeAt(SIDE.UP) + removeAt(SIDE.DOWN) + return this + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt new file mode 100644 index 00000000..781f8dcd --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/FlexPanel.kt @@ -0,0 +1,157 @@ +package pl.treksoft.kvision.panel + +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.core.WidgetWrapper +import pl.treksoft.kvision.snabbdom.StringPair + +enum class FLEXDIR(val dir: String) { + ROW("row"), + ROWREV("row-reverse"), + COLUMN("column"), + COLUMNREV("column-reverse") +} + +enum class FLEXWRAP(val wrap: String) { + NOWRAP("nowrap"), + WRAP("wrap"), + WRAPREV("wrap-reverse") +} + +enum class FLEXJUSTIFY(val justify: String) { + FLEXSTART("flex-start"), + FLEXEND("flex-end"), + CENTER("center"), + SPACEBETWEEN("space-between"), + SPACEAROUND("space-around"), + SPACEEVENLY("space-evenly") +} + +enum class FLEXALIGNITEMS(val alignItems: String) { + FLEXSTART("flex-start"), + FLEXEND("flex-end"), + CENTER("center"), + BASELINE("baseline"), + STRETCH("stretch") +} + +enum class FLEXALIGNCONTENT(val alignContent: String) { + FLEXSTART("flex-start"), + FLEXEND("flex-end"), + CENTER("center"), + SPACEBETWEEN("space-between"), + SPACEAROUND("space-around"), + STRETCH("stretch") +} + +open class FlexPanel(direction: FLEXDIR? = null, wrap: FLEXWRAP? = null, justify: FLEXJUSTIFY? = null, + alignItems: FLEXALIGNITEMS? = null, alignContent: FLEXALIGNCONTENT? = null, + classes: Set = setOf()) : Container(classes) { + var direction = direction + set(value) { + field = value + refresh() + } + var wrap = wrap + set(value) { + field = value + refresh() + } + var justify = justify + set(value) { + field = value + refresh() + } + var alignItems = alignItems + set(value) { + field = value + refresh() + } + var alignContent = alignContent + set(value) { + field = value + refresh() + } + + @Suppress("LongParameterList") + fun add(child: Widget, order: Int? = null, grow: Int? = null, shrink: Int? = null, + basis: Int? = null, alignSelf: FLEXALIGNITEMS? = null, classes: Set = setOf()): Container { + return addInternal(FlexWrapper(child, order, grow, shrink, basis, alignSelf, classes)) + } + + override fun add(child: Widget): Container { + return add(child, null) + } + + override fun addAll(children: List): Container { + children.forEach { add(it, null) } + return this + } + + override fun remove(child: Widget): Container { + children.find { (it as FlexWrapper).delegate == child }?.let { + super.remove(it) + it.dispose() + } + return this + } + + override fun removeAll(): Container { + children.map { + it.clearParent() + it.dispose() + } + children.clear() + refresh() + return this + } + + override fun getSnStyle(): List { + val snstyle = super.getSnStyle().toMutableList() + snstyle.add("display" to "flex") + direction?.let { + snstyle.add("flex-direction" to it.dir) + } + wrap?.let { + snstyle.add("flex-wrap" to it.wrap) + } + justify?.let { + snstyle.add("justify-content" to it.justify) + } + alignItems?.let { + snstyle.add("align-items" to it.alignItems) + } + alignContent?.let { + snstyle.add("align-content" to it.alignContent) + } + println("abc") + return snstyle + } +} + +class FlexWrapper(delegate: Widget, private val order: Int? = null, private val grow: Int? = null, + private val shrink: Int? = null, private val basis: Int? = null, + private val alignSelf: FLEXALIGNITEMS? = null, + classes: Set = setOf()) : WidgetWrapper(delegate, classes) { + + override fun getSnStyle(): List { + val snstyle = super.getSnStyle().toMutableList() + order?.let { + snstyle.add("order" to "$it") + } + grow?.let { + snstyle.add("flex-grow" to "$it") + } + shrink?.let { + snstyle.add("flex-shrink" to "$it") + } + basis?.let { + snstyle.add("flex-basis" to "$it%") + } + alignSelf?.let { + snstyle.add("align-self" to it.alignItems) + } + return snstyle + } + +} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt index f7adfe8f..44ba509f 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/GridPanel.kt @@ -1,152 +1,226 @@ package pl.treksoft.kvision.panel -import com.github.snabbdom.VNode import pl.treksoft.kvision.core.Container import pl.treksoft.kvision.core.Widget import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.ALIGN -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag +import pl.treksoft.kvision.snabbdom.StringPair -enum class GRIDTYPE { - BOOTSTRAP, - DSG +enum class GRIDJUSTIFY(val justify: String) { + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch") } -enum class GRIDSIZE(val size: String) { - XS("xs"), - SM("sm"), - MD("md"), - LG("lg") +enum class GRIDALIGN(val align: String) { + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch") } -const val FULLPERCENT = 100 -const val MAX_COLUMNS = 12 +enum class GRIDJUSTIFYCONTENT(val justifyContent: String) { + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch"), + SPACEAROUND("space-around"), + SPACEBETWEEN("space-between"), + SPACEEVENLY("space-evenly") +} -internal data class WidgetParam(val widget: Widget, val size: Int, val offset: Int) +enum class GRIDALIGNCONTENT(val alignContent: String) { + START("start"), + END("end"), + CENTER("center"), + STRETCH("stretch"), + SPACEAROUND("space-around"), + SPACEBETWEEN("space-between"), + SPACEEVENLY("space-evenly") +} -open class GridPanel(private val gridtype: GRIDTYPE = GRIDTYPE.BOOTSTRAP, private val gridsize: GRIDSIZE = GRIDSIZE.MD, - protected var rows: Int = 0, protected var cols: Int = 0, align: ALIGN = ALIGN.NONE, - classes: Set = setOf()) : Container(classes) { - private var align = align +enum class GRIDFLOW(val flow: String) { + ROW("row"), + COLUMN("column"), + ROWDENSE("row dense"), + COLUMNDENSE("column dense") +} + +open class GridPanel(autoColumns: String? = null, autoRows: String? = null, autoFlow: GRIDFLOW? = null, + templateColumns: String? = null, templateRows: String? = null, templateAreas: List? = null, + columnGap: Int? = null, rowGap: Int? = null, justifyItems: GRIDJUSTIFY? = null, + alignItems: GRIDALIGN? = null, justifyContent: GRIDJUSTIFYCONTENT? = null, + alignContent: GRIDALIGNCONTENT? = null, classes: Set = setOf()) : Container(classes) { + var autoColumns = autoColumns + set(value) { + field = value + refresh() + } + var autoRows = autoRows + set(value) { + field = value + refresh() + } + var autoFlow = autoFlow + set(value) { + field = value + refresh() + } + var templateColumns = templateColumns + set(value) { + field = value + refresh() + } + var templateRows = templateRows + set(value) { + field = value + refresh() + } + var templateAreas = templateAreas + set(value) { + field = value + refresh() + } + var columnGap = columnGap + set(value) { + field = value + refresh() + } + var rowGap = rowGap + set(value) { + field = value + refresh() + } + var justifyItems = justifyItems + set(value) { + field = value + refresh() + } + var alignItems = alignItems + set(value) { + field = value + refresh() + } + var justifyContent = justifyContent + set(value) { + field = value + refresh() + } + var alignContent = alignContent set(value) { field = value refresh() } - internal val map = mutableMapOf>() - private var auto: Boolean = true - - open fun add(child: Widget, row: Int, col: Int, size: Int = 0, offset: Int = 0): Container { - val cRow = if (row < 0) 0 else row - val cCol = if (col < 0) 0 else col - if (row > rows - 1) rows = cRow + 1 - if (col > cols - 1) cols = cCol + 1 - map.getOrPut(cRow, { mutableMapOf() }).put(cCol, WidgetParam(child, size, offset)) - if (size > 0 || offset > 0) auto = false - return this + @Suppress("LongParameterList") + fun add(child: Widget, columnStart: Int? = null, rowStart: Int? = null, + columnEnd: String? = null, rowEnd: String? = null, area: String? = null, justifySelf: GRIDJUSTIFY? = null, + alignSelf: GRIDALIGN? = null, classes: Set = setOf()): Container { + return addInternal(GridWrapper(child, columnStart, rowStart, columnEnd, rowEnd, area, justifySelf, + alignSelf, classes)) } override fun add(child: Widget): Container { - return this.add(child, 0, this.cols) + return add(child, null, null) } override fun addAll(children: List): Container { - children.forEach { this.add(it) } + children.forEach { add(it, null, null) } return this } override fun remove(child: Widget): Container { - for (i in 0 until rows) { - val row = map[i] - if (row != null) { - for (j in 0 until cols) { - val wp = row[j] - if (wp != null) { - if (wp.widget == child) row.remove(j) - } - } - } + children.find { (it as GridWrapper).delegate == child }?.let { + super.remove(it) + it.dispose() } return this } - open fun removeAt(row: Int, col: Int): Container { - map[row]?.remove(col) + override fun removeAll(): Container { + children.map { + it.clearParent() + it.dispose() + } + children.clear() + refresh() return this } - override fun removeAt(index: Int): Container { - return this.removeAt(0, index) - } - - override fun childrenVNodes(): Array { - return if (gridtype == GRIDTYPE.BOOTSTRAP) { - childrenVNodesBts() - } else { - childrenVNodesDsg() + @Suppress("ComplexMethod") + override fun getSnStyle(): List { + val snstyle = super.getSnStyle().toMutableList() + snstyle.add("display" to "grid") + autoColumns?.let { + snstyle.add("grid-auto-columns" to it) + } + autoRows?.let { + snstyle.add("grid-auto-rows" to it) + } + autoFlow?.let { + snstyle.add("grid-auto-flow" to it.flow) + } + templateColumns?.let { + snstyle.add("grid-template-columns" to it) } + templateRows?.let { + snstyle.add("grid-template-rows" to it) + } + templateAreas?.let { + snstyle.add("grid-template-areas" to it.joinToString("\n")) + } + columnGap?.let { + snstyle.add("grid-column-gap" to "${it}px") + } + rowGap?.let { + snstyle.add("grid-row-gap" to "${it}px") + } + justifyItems?.let { + snstyle.add("justify-items" to it.justify) + } + alignItems?.let { + snstyle.add("align-items" to it.align) + } + justifyContent?.let { + snstyle.add("justify-content" to it.justifyContent) + } + alignContent?.let { + snstyle.add("align-content" to it.alignContent) + } + return snstyle } +} - @Suppress("NestedBlockDepth", "LoopToCallChain") - protected open fun childrenVNodesDsg(): Array { - val ret = mutableListOf() - val num = FULLPERCENT / cols - for (i in 0 until rows) { - val rowContainer = Container(setOf("dsgrow")) - val row = map[i] - if (row != null) { - for (j in 0 until cols) { - val wp = row[j] - val widget = wp?.widget?.let { WidgetWrapper(it, setOf("dsgcol")) } ?: - Tag(TAG.DIV, classes = setOf("dsgcol")) - widget.widthPercent = num - if (align != ALIGN.NONE) { - widget.addCssClass(align.className) - } - rowContainer.add(widget) - } - } - ret.add(rowContainer.render()) - } - return ret.toTypedArray() - } +class GridWrapper(delegate: Widget, private val columnStart: Int? = null, private val rowStart: Int? = null, + private val columnEnd: String? = null, private val rowEnd: String? = null, + private val area: String? = null, private val justifySelf: GRIDJUSTIFY? = null, + private val alignSelf: GRIDALIGN? = null, + classes: Set = setOf()) : WidgetWrapper(delegate, classes) { - @Suppress("NestedBlockDepth", "LoopToCallChain") - private fun childrenVNodesBts(): Array { - val ret = mutableListOf() - val num = MAX_COLUMNS / cols - for (i in 0 until rows) { - val rowContainer = Container(setOf("row")) - val row = map[i] - if (row != null) { - for (j in 0 until cols) { - val wp = row[j] - if (auto) { - val widget = wp?.widget?.let { - WidgetWrapper(it, setOf("col-" + gridsize.size + "-" + num)) - } ?: Tag(TAG.DIV, classes = setOf("col-" + gridsize.size + "-" + num)) - if (align != ALIGN.NONE) { - widget.addCssClass(align.className) - } - rowContainer.add(widget) - } else { - if (wp != null) { - val s = if (wp.size > 0) wp.size else num - val widget = WidgetWrapper(wp.widget, setOf("col-" + gridsize.size + "-" + s)) - if (wp.offset > 0) { - widget.addCssClass("col-" + gridsize.size + "-offset-" + wp.offset) - } - if (align != ALIGN.NONE) { - widget.addCssClass(align.className) - } - rowContainer.add(widget) - } - } - } - } - ret.add(rowContainer.render()) - } - return ret.toTypedArray() + override fun getSnStyle(): List { + val snstyle = super.getSnStyle().toMutableList() + columnStart?.let { + snstyle.add("grid-column-start" to "$it") + } + rowStart?.let { + snstyle.add("grid-row-start" to "$it") + } + columnEnd?.let { + snstyle.add("grid-column-end" to it) + } + rowEnd?.let { + snstyle.add("grid-row-end" to it) + } + area?.let { + snstyle.add("grid-area" to it) + } + justifySelf?.let { + snstyle.add("justify-self" to it.justify) + } + alignSelf?.let { + snstyle.add("align-self" to it.align) + } + return snstyle } + } diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt index 3d1db0e7..c9317374 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/HPanel.kt @@ -1,33 +1,5 @@ package pl.treksoft.kvision.panel -import com.github.snabbdom.VNode -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.core.WidgetWrapper -import pl.treksoft.kvision.html.ALIGN -import pl.treksoft.kvision.html.TAG -import pl.treksoft.kvision.html.Tag - -open class HPanel(align: ALIGN = ALIGN.NONE, classes: Set = setOf()) : GridPanel(GRIDTYPE.DSG, align = align, - classes = classes) { - - override fun add(child: Widget, row: Int, col: Int, size: Int, offset: Int): Container { - return super.add(child, 0, col, size, offset) - } - - override fun childrenVNodesDsg(): Array { - val ret = mutableListOf() - val rowContainer = Container(setOf("dsgrow")) - val row = map[0] - if (row != null) { - for (j in 0 until cols) { - val wp = row[j] - val widget = wp?.widget?.let { WidgetWrapper(it, setOf("dsgcolf")) } ?: - Tag(TAG.DIV, classes = setOf("dsgcolf")) - rowContainer.add(widget) - } - } - ret.add(rowContainer.render()) - return ret.toTypedArray() - } -} +open class HPanel(justify: FLEXJUSTIFY? = null, alignItems: FLEXALIGNITEMS? = null, + classes: Set = setOf()) : FlexPanel(null, + null, justify, alignItems, null, classes) diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt new file mode 100644 index 00000000..af4dd53f --- /dev/null +++ b/src/main/kotlin/pl/treksoft/kvision/panel/ResponsiveGridPanel.kt @@ -0,0 +1,117 @@ +package pl.treksoft.kvision.panel + +import pl.treksoft.kvision.core.Container +import pl.treksoft.kvision.core.Widget +import pl.treksoft.kvision.core.WidgetWrapper +import pl.treksoft.kvision.html.ALIGN +import pl.treksoft.kvision.html.TAG +import pl.treksoft.kvision.html.Tag + +enum class GRIDSIZE(val size: String) { + XS("xs"), + SM("sm"), + MD("md"), + LG("lg") +} + +const val MAX_COLUMNS = 12 + +internal data class WidgetParam(val widget: Widget, val size: Int, val offset: Int) + +open class ResponsiveGridPanel(private val gridsize: GRIDSIZE = GRIDSIZE.MD, + private var rows: Int = 0, private var cols: Int = 0, align: ALIGN = ALIGN.NONE, + classes: Set = setOf()) : Container(classes) { + protected var align = align + set(value) { + field = value + refresh() + } + + internal val map = mutableMapOf>() + private var auto: Boolean = true + + open fun add(child: Widget, row: Int, col: Int, size: Int = 0, offset: Int = 0): Container { + val cRow = if (row < 0) 0 else row + val cCol = if (col < 0) 0 else col + if (row > rows - 1) rows = cRow + 1 + if (col > cols - 1) cols = cCol + 1 + map.getOrPut(cRow, { mutableMapOf() }).put(cCol, WidgetParam(child, size, offset)) + if (size > 0 || offset > 0) auto = false + refreshRowContainers() + return this + } + + override fun add(child: Widget): Container { + return this.add(child, 0, this.cols) + } + + override fun addAll(children: List): Container { + children.forEach { this.add(it) } + return this + } + + @Suppress("NestedBlockDepth") + override fun remove(child: Widget): Container { + for (i in 0 until rows) { + val row = map[i] + if (row != null) { + for (j in 0 until cols) { + val wp = row[j] + if (wp != null) { + if (wp.widget == child) row.remove(j) + } + } + } + } + refreshRowContainers() + return this + } + + open fun removeAt(row: Int, col: Int): Container { + map[row]?.remove(col) + refreshRowContainers() + return this + } + + @Suppress("ComplexMethod", "NestedBlockDepth") + protected open fun refreshRowContainers() { + clearRowContainers() + val num = MAX_COLUMNS / cols + for (i in 0 until rows) { + val rowContainer = Container(setOf("row")) + val row = map[i] + if (row != null) { + for (j in 0 until cols) { + val wp = row[j] + if (auto) { + val widget = wp?.widget?.let { + WidgetWrapper(it, setOf("col-" + gridsize.size + "-" + num)) + } ?: Tag(TAG.DIV, classes = setOf("col-" + gridsize.size + "-" + num)) + if (align != ALIGN.NONE) { + widget.addCssClass(align.className) + } + rowContainer.add(widget) + } else { + if (wp != null) { + val s = if (wp.size > 0) wp.size else num + val widget = WidgetWrapper(wp.widget, setOf("col-" + gridsize.size + "-" + s)) + if (wp.offset > 0) { + widget.addCssClass("col-" + gridsize.size + "-offset-" + wp.offset) + } + if (align != ALIGN.NONE) { + widget.addCssClass(align.className) + } + rowContainer.add(widget) + } + } + } + } + addInternal(rowContainer) + } + } + + private fun clearRowContainers() { + children.forEach { it.dispose() } + removeAll() + } +} diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/SplitPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/SplitPanel.kt index 57519833..569a5b85 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/SplitPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/SplitPanel.kt @@ -48,7 +48,7 @@ open class SplitPanel(private val direction: DIRECTION = DIRECTION.VERTICAL, override fun childrenVNodes(): Array { return if (children.size == 2) { - arrayOf(children[0].render(), splitter.render(), children[1].render()) + arrayOf(children[0].renderVNode(), splitter.renderVNode(), children[1].renderVNode()) } else { arrayOf() } diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt index 372098b8..c5162c0a 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/StackPanel.kt @@ -14,7 +14,7 @@ open class StackPanel(private val activateLast: Boolean = true, override fun childrenVNodes(): Array { return if (activeIndex >= 0 && activeIndex < children.size) { - arrayOf(children[activeIndex].render()) + arrayOf(children[activeIndex].renderVNode()) } else { arrayOf() } @@ -40,12 +40,6 @@ open class StackPanel(private val activateLast: Boolean = true, return this } - override fun removeAt(index: Int): Container { - super.removeAt(index) - if (activeIndex > children.size - 1) activeIndex = children.size - 1 - return this - } - override fun removeAll(): Container { super.removeAll() if (activeIndex > children.size - 1) activeIndex = children.size - 1 diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt index 0022023b..817ecaa0 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/TabPanel.kt @@ -47,8 +47,8 @@ open class TabPanel : Container(setOf()) { } open fun removeTab(index: Int): TabPanel { - nav.removeAt(index) - content.removeAt(index) + nav.remove(nav.children[index]) + content.remove(content.children[index]) activeIndex = content.activeIndex return this } diff --git a/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt b/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt index f81c4a7e..42e33637 100644 --- a/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt +++ b/src/main/kotlin/pl/treksoft/kvision/panel/VPanel.kt @@ -1,27 +1,5 @@ package pl.treksoft.kvision.panel -import pl.treksoft.kvision.core.Container -import pl.treksoft.kvision.core.Widget -import pl.treksoft.kvision.html.ALIGN - -open class VPanel(align: ALIGN = ALIGN.NONE, classes: Set = setOf()) : GridPanel(GRIDTYPE.BOOTSTRAP, - align = align, classes = classes) { - - override fun add(child: Widget, row: Int, col: Int, size: Int, offset: Int): Container { - return super.add(child, row, 0, size, offset) - } - - override fun add(child: Widget): Container { - return this.add(child, this.rows, 0) - } - - override fun addAll(children: List): Container { - children.forEach { this.add(it) } - return this - } - - override fun removeAt(index: Int): Container { - return this.removeAt(index, 0) - } - -} +open class VPanel(justify: FLEXJUSTIFY? = null, alignItems: FLEXALIGNITEMS? = null, + classes: Set = setOf()) : FlexPanel(FLEXDIR.COLUMN, + null, justify, alignItems, null, classes) diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt index baed1372..c39aeb75 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/ContainerSpec.kt @@ -64,24 +64,6 @@ class ContainerSpec : DomSpec { } } - @Test - fun removeAt() { - run { - val root = Root("test") - val container = Container() - val child1 = Widget() - child1.id = "child1" - val child2 = Widget() - child2.id = "child2" - container.add(child1) - container.add(child2) - root.add(container) - container.removeAt(0) - val elem1 = document.getElementById("child1") - val elem2 = document.getElementById("child2") - assertTrue("Container renders children") { elem1 == null && elem2 != null } - } - } @Test fun removeAll() { run { diff --git a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt index 4117b03d..179788d0 100644 --- a/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt +++ b/src/test/kotlin/test/pl/treksoft/kvision/core/WidgetSpec.kt @@ -10,11 +10,11 @@ import kotlin.test.assertTrue class WidgetSpec : WSpec { @Test - fun render() { + fun renderVNode() { runW { widget, _ -> widget.addCssClass("testClass") widget.title = "test_title" - val vnode = widget.render() + val vnode = widget.renderVNode() assertTrue("VNode has correct class attribute") { vnode.data.`class`.testClass == true } assertTrue("VNode has correct id attribute") { vnode.data.attrs.id == "test_id" } assertTrue("VNode has correct title attribute") { vnode.data.attrs.title == "test_title" } -- cgit