-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Vertical Scrollable List Fragment #373
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
zircon.core/src/commonMain/kotlin/org/hexworks/zircon/api/fragment/ScrollableList.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package org.hexworks.zircon.api.fragment | ||
|
||
import org.hexworks.zircon.api.Beta | ||
import org.hexworks.zircon.api.component.Fragment | ||
|
||
@Beta | ||
interface ScrollableList<T> : Fragment { | ||
val items: List<T> | ||
fun scrollTo(idx: Int) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,10 +8,12 @@ import org.hexworks.zircon.api.data.Position | |
import org.hexworks.zircon.api.data.Tile | ||
import org.hexworks.zircon.api.graphics.TileGraphics | ||
|
||
@Suppress("DuplicatedCode") | ||
class VerticalScrollBarRenderer : ComponentRenderer<ScrollBar> { | ||
open class VerticalScrollBarRenderer internal constructor() : ComponentRenderer<ScrollBar> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you make it |
||
open val aboveBarCharacter: Char = ' ' | ||
open val belowBarCharacter: Char = ' ' | ||
open val barCharacter: Char = ' ' | ||
|
||
override fun render(tileGraphics: TileGraphics, context: ComponentRenderContext<ScrollBar>) { | ||
final override fun render(tileGraphics: TileGraphics, context: ComponentRenderContext<ScrollBar>) { | ||
val defaultStyleSet = context.componentStyle.fetchStyleFor(ComponentState.DEFAULT) | ||
val invertedDefaultStyleSet = defaultStyleSet | ||
.withBackgroundColor(defaultStyleSet.foregroundColor) | ||
|
@@ -24,18 +26,18 @@ class VerticalScrollBarRenderer : ComponentRenderer<ScrollBar> { | |
|
||
tileGraphics.applyStyle(context.currentStyle) | ||
|
||
(0..totalScrollBarHeight).forEach { idx -> | ||
(0 until totalScrollBarHeight).forEach { idx -> | ||
when { | ||
idx < lowBarPosition -> tileGraphics.draw( | ||
Tile.createCharacterTile(' ', disabledStyleSet), | ||
Tile.createCharacterTile(aboveBarCharacter, disabledStyleSet), | ||
Position.create(0, idx) | ||
) | ||
idx > highBarPosition -> tileGraphics.draw( | ||
Tile.createCharacterTile(' ', disabledStyleSet), | ||
Tile.createCharacterTile(belowBarCharacter, disabledStyleSet), | ||
Position.create(0, idx) | ||
) | ||
else -> tileGraphics.draw( | ||
Tile.createCharacterTile(' ', invertedDefaultStyleSet), | ||
Tile.createCharacterTile(barCharacter, invertedDefaultStyleSet), | ||
Position.create(0, idx) | ||
) | ||
} | ||
|
138 changes: 138 additions & 0 deletions
138
...rc/commonMain/kotlin/org/hexworks/zircon/internal/fragment/impl/VerticalScrollableList.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package org.hexworks.zircon.internal.fragment.impl | ||
|
||
import org.hexworks.zircon.api.Components | ||
import org.hexworks.zircon.api.component.Label | ||
import org.hexworks.zircon.api.component.ScrollBar | ||
import org.hexworks.zircon.api.component.renderer.ComponentRenderer | ||
import org.hexworks.zircon.api.data.Position | ||
import org.hexworks.zircon.api.data.Size | ||
import org.hexworks.zircon.api.fragment.ScrollableList | ||
import org.hexworks.zircon.api.graphics.Symbols | ||
import org.hexworks.zircon.api.uievent.ComponentEventType | ||
|
||
/** | ||
* This creates a vertically-scrolling list. You provide a list of [items] and a subset of them are rendered with | ||
* a scrollbar on the right side. | ||
* | ||
* ### Navigation | ||
* * To scroll by **single row**, you can click or activate the top/bottom arrows. Arrow keys while the bar is focused also | ||
* works. | ||
* * To scroll by **step** (small jumps), you can click on the empty parts above or below the bar, or use the mouse wheel. | ||
* * You can also click and drag the bar itself. | ||
* | ||
* ### Limitations | ||
* * [items] is immutable. You can't change the list after its created. | ||
* * [items] aren't focusable. You can click on them, but you can't tab to them. | ||
* * Each [item][items] can't span multiple lines. It will be clipped if it's too long. | ||
* * Even if [items] fully fits in [size], a scrollbar will still be displayed. | ||
*/ | ||
class VerticalScrollableList<T>( | ||
private val size: Size, | ||
position: Position, | ||
override val items: List<T>, | ||
/** Handler for when an item in the list is activated. */ | ||
private val onItemActivated: (item: T, idx: Int) -> Unit = { _, _ -> }, | ||
/** Transform items in [items] into displayable strings. */ | ||
private val renderItem: (T) -> String = { it.toString() }, | ||
/** If set, use this instead of the default [ComponentRenderer] for the [ScrollBar] created internally. */ | ||
private val scrollbarRenderer: ComponentRenderer<ScrollBar>? = null | ||
) : ScrollableList<T> { | ||
/** Reusable list of labels we display in the main scroll panel. */ | ||
private val labels = mutableListOf<Label>() | ||
|
||
/** Index in [items] of the top item we're showing in the main scroll panel. */ | ||
private var topItemIdx: Int = 0 | ||
|
||
override val root = Components.hbox() | ||
.withSize(size) | ||
.withPosition(position) | ||
.withSpacing(0) | ||
.build() | ||
|
||
private val scrollPanel = Components.vbox() | ||
.withSize(size.withRelativeWidth(-1)) | ||
.withDecorations() | ||
.withSpacing(0) | ||
.build() | ||
|
||
private val scrollBarVbox = Components.vbox() | ||
.withSize(size.withWidth(1)) | ||
.withDecorations() | ||
.withSpacing(0) | ||
.build() | ||
|
||
private val actualScrollbar: ScrollBar = Components.verticalScrollbar() | ||
.withSize(1, size.height - 2) | ||
.withItemsShownAtOnce(size.height) | ||
.withNumberOfScrollableItems(items.size) | ||
.withDecorations() | ||
.also { builder -> | ||
scrollbarRenderer?.let { builder.withComponentRenderer(it) } | ||
} | ||
.build() | ||
|
||
private val decrementButton = Components.button() | ||
.withText("${Symbols.TRIANGLE_UP_POINTING_BLACK}") | ||
.withSize(1, 1) | ||
.withDecorations() | ||
.build() | ||
|
||
private val incrementButton = Components.button() | ||
.withText("${Symbols.TRIANGLE_DOWN_POINTING_BLACK}") | ||
.withSize(1, 1) | ||
.withDecorations() | ||
.build() | ||
|
||
init { | ||
root.addComponents(scrollPanel, scrollBarVbox) | ||
|
||
decrementButton.processComponentEvents(ComponentEventType.ACTIVATED) { | ||
actualScrollbar.decrementValues() | ||
} | ||
incrementButton.processComponentEvents(ComponentEventType.ACTIVATED) { | ||
actualScrollbar.incrementValues() | ||
} | ||
|
||
actualScrollbar.onValueChange { | ||
scrollTo(it.newValue) | ||
} | ||
|
||
scrollBarVbox.addComponents(decrementButton, actualScrollbar, incrementButton) | ||
|
||
displayListFromIndex() | ||
} | ||
|
||
override fun scrollTo(idx: Int) { | ||
topItemIdx = idx | ||
displayListFromIndex() | ||
} | ||
|
||
private fun displayListFromIndex() { | ||
val maxIdx = when { | ||
topItemIdx + size.height < items.size -> topItemIdx + size.height | ||
else -> items.size | ||
} | ||
for (idx in topItemIdx until maxIdx) { | ||
val labelIdx = idx - topItemIdx | ||
// Generate and add labels until we have enough for the current entry | ||
while (labelIdx > labels.lastIndex) { | ||
labels.add(Components.label() | ||
.withDecorations() | ||
.withSize(scrollPanel.contentSize.withHeight(1)) | ||
.build() | ||
.also { label -> | ||
scrollPanel.addComponent(label) | ||
label.onActivated { | ||
onItemActivated(items[topItemIdx + labelIdx], topItemIdx + labelIdx) | ||
} | ||
} | ||
) | ||
} | ||
labels[labelIdx].text = renderItem(items[idx]) | ||
} | ||
// Clear any remaining labels, just in case | ||
for (labelIdx in (maxIdx - topItemIdx) until labels.size) { | ||
labels[labelIdx].text = "" | ||
} | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
zircon.core/src/commonTest/kotlin/org/hexworks/zircon/internal/renderer/TestRenderer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.hexworks.zircon.internal.renderer | ||
|
||
import org.hexworks.cobalt.databinding.api.extension.toProperty | ||
import org.hexworks.cobalt.databinding.api.property.Property | ||
import org.hexworks.cobalt.databinding.api.value.ObservableValue | ||
import org.hexworks.zircon.api.behavior.Clearable | ||
import org.hexworks.zircon.api.component.ComponentContainer | ||
import org.hexworks.zircon.api.data.Size | ||
import org.hexworks.zircon.api.graphics.TileGraphics | ||
import org.hexworks.zircon.api.grid.TileGrid | ||
import org.hexworks.zircon.api.resource.TilesetResource | ||
import org.hexworks.zircon.api.uievent.UIEvent | ||
import org.hexworks.zircon.api.uievent.UIEventResponse | ||
import org.hexworks.zircon.api.view.base.BaseView | ||
import org.hexworks.zircon.internal.behavior.RenderableContainer | ||
import org.hexworks.zircon.internal.config.RuntimeConfig | ||
import org.hexworks.zircon.internal.graphics.FastTileGraphics | ||
import org.hexworks.zircon.internal.grid.ThreadSafeTileGrid | ||
import org.hexworks.zircon.internal.uievent.UIEventDispatcher | ||
|
||
/** | ||
* This is a simple test renderer that draws things back into the provided [tileGraphics]. After instantiation, | ||
* you should call [withComponentContainer] to add components and fragments, and then call [render] to see the result. | ||
* You can also [dispatch] events to interact with it. | ||
* | ||
* @sample org.hexworks.zircon.internal.renderer.TestRendererTest.tinyExample | ||
*/ | ||
class TestRenderer( | ||
private val tileGraphics: TileGraphics, | ||
tileset: TilesetResource = RuntimeConfig.config.defaultTileset, | ||
gridSize: Size = Size.defaultGridSize() | ||
) : UIEventDispatcher, Renderer, Clearable { | ||
private val tileGrid: TileGrid = ThreadSafeTileGrid(tileset, gridSize) | ||
private val mainView = object : BaseView(tileGrid) {} | ||
private val closedValueProperty: Property<Boolean> = false.toProperty() | ||
override val closedValue: ObservableValue<Boolean> get() = closedValueProperty | ||
|
||
init { | ||
mainView.dock() | ||
} | ||
|
||
fun withComponentContainer(cb: ComponentContainer.() -> Unit) { | ||
with(mainView.screen, cb) | ||
} | ||
|
||
override fun create() { | ||
} | ||
|
||
override fun clear() { | ||
tileGraphics.clear() | ||
} | ||
|
||
override fun render() { | ||
(tileGrid as RenderableContainer).renderables.forEach { renderable -> | ||
if (!renderable.isHidden) { | ||
val graphics = FastTileGraphics( | ||
initialSize = renderable.size, | ||
initialTileset = renderable.tileset, | ||
initialTiles = mapOf() | ||
) | ||
renderable.render(graphics) | ||
graphics.contents().forEach { (pos, tile) -> | ||
tileGraphics.draw(tile, pos + renderable.position) | ||
} | ||
} | ||
} | ||
} | ||
|
||
override fun dispatch(event: UIEvent): UIEventResponse = (mainView.screen as UIEventDispatcher).dispatch(event) | ||
|
||
override fun close() { | ||
if (!closedValueProperty.value) { | ||
tileGrid.close() | ||
closedValueProperty.value = true | ||
} | ||
} | ||
|
||
override val isClosed: ObservableValue<Boolean> get() = closedValue | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really cool!