ui: implement tab navigation

This commit is contained in:
rr-
2018-11-08 22:07:04 +01:00
parent 904951acf4
commit 284f549966
10 changed files with 163 additions and 45 deletions
-2
View File
@@ -63,8 +63,6 @@ def bind_commands() -> None:
}.items():
for key_variant in {key, key.upper()}:
urwid.command_map[key_variant] = f"cursor {direction}"
urwid.command_map["tab"] = f"cursor down"
urwid.command_map["shift tab"] = f"cursor up"
class Ui:
+2 -3
View File
@@ -4,6 +4,7 @@ import urwid
from pqcli.ui.custom_button import CustomButton
from pqcli.ui.custom_line_box import CustomLineBox
from pqcli.ui.layout import NPile
class ConfirmDialog(urwid.Overlay):
@@ -19,9 +20,7 @@ class ConfirmDialog(urwid.Overlay):
)
line_box = CustomLineBox(
urwid.ListBox(
urwid.SimpleFocusListWalker([question, no_btn, yes_btn])
)
urwid.Filler(NPile([question, no_btn, yes_btn], outermost=True))
)
super().__init__(
+4 -2
View File
@@ -2,8 +2,10 @@ import typing as T
import urwid
from pqcli.ui.layout import NColumns, NPile
class DataTable(urwid.Pile):
class DataTable(NPile):
def __init__(self, columns: T.Any) -> None:
self.column_count = len(columns)
self._columns = columns
@@ -25,7 +27,7 @@ class DataTable(urwid.Pile):
self.contents.append(
(
urwid.Columns(
NColumns(
list((*x, y) for x, y in zip(self._columns, widgets))
),
(urwid.PACK, 1),
+3 -3
View File
@@ -1,5 +1,7 @@
import urwid
from pqcli.ui.layout import NPile
class DoubleLineBox(urwid.AttrMap):
def __init__(
@@ -23,9 +25,7 @@ class DoubleLineBox(urwid.AttrMap):
)
super().__init__(
urwid.Pile(
[self.top_line_box, (urwid.PACK, self.bottom_line_box)]
),
NPile([self.top_line_box, (urwid.PACK, self.bottom_line_box)]),
"linebox",
"linebox-focus",
)
+11 -10
View File
@@ -18,8 +18,9 @@ from pqcli.ui.custom_line_box import CustomLineBox
from pqcli.ui.custom_progress_bar import CustomProgressBar
from pqcli.ui.data_table import DataTable
from pqcli.ui.double_line_box import DoubleLineBox
from pqcli.ui.layout import NColumns, NPile
from pqcli.ui.read_only_check_box import ReadOnlyCheckBox
from pqcli.ui.scrollable import ScrollBar, Scrollable
from pqcli.ui.scrollable import Scrollable, ScrollBar
class CharacterSheetView(DoubleLineBox):
@@ -301,7 +302,7 @@ class QuestBookView(DoubleLineBox):
self.quest_bar.set_max(self.player.quest_book.quest_bar.max_)
class TaskView(urwid.Pile):
class TaskView(NPile):
def __init__(self, player: Player) -> None:
self.player = player
@@ -331,7 +332,7 @@ class TaskView(urwid.Pile):
return (size[0], 2)
class GameView(urwid.Pile):
class GameView(NPile):
signals = ["cancel"]
def __init__(
@@ -351,31 +352,31 @@ class GameView(urwid.Pile):
self.quest_book_view = QuestBookView(player)
self.task_view = TaskView(player)
self.columns = urwid.Columns(
self.columns = NColumns(
[
(
urwid.WEIGHT,
1,
urwid.Pile(
NPile(
[(19, self.character_sheet_view), self.spell_book_view]
),
),
(
urwid.WEIGHT,
2,
urwid.Pile(
[(15, self.equipment_view), self.inventory_view]
),
NPile([(15, self.equipment_view), self.inventory_view]),
),
(
urwid.WEIGHT,
2,
urwid.Pile([(15, self.plot_view), self.quest_book_view]),
NPile([(15, self.plot_view), self.quest_book_view]),
),
]
)
super().__init__([self.columns, (urwid.PACK, self.task_view)])
super().__init__(
[self.columns, (urwid.PACK, self.task_view)], outermost=True
)
def cancel(self) -> None:
self._emit("cancel")
+119
View File
@@ -0,0 +1,119 @@
# Copyright (c) 2013-2016 Christian Geier et al.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import typing as T
import urwid
class SupportsNext:
"""
Classes inheriting from SupportsNext must implement the following methods:
_select_first_selectable
_select_last_selectable
"""
def __init__(self, *args, **kwargs):
self.outermost = kwargs.get("outermost", False)
if "outermost" in kwargs:
kwargs.pop("outermost")
super().__init__(*args, **kwargs)
class NextMixin(SupportsNext):
"""Implements SupportsNext for urwid.Pile and urwid.Columns"""
def _select_first_selectable(self):
"""
select our first selectable item
(recursively if that item SupportsNext)
"""
i = self._first_selectable()
self.set_focus(i)
if isinstance(self.contents[i][0], SupportsNext):
self.contents[i][0]._select_first_selectable()
def _select_last_selectable(self):
"""
select our last selectable item
(recursively if that item SupportsNext)
"""
i = self._last_selectable()
self.set_focus(i)
if isinstance(self._contents[i][0], SupportsNext):
self.contents[i][0]._select_last_selectable()
def _first_selectable(self):
"""return sequence number of self.contents last selectable item"""
for j in range(0, len(self._contents)):
if self._contents[j][0].selectable():
return j
return False
def _last_selectable(self):
"""return sequence number of self._contents last selectable item"""
for j in range(len(self._contents) - 1, -1, -1):
if self._contents[j][0].selectable():
return j
return False
def keypress(self, size, key) -> T.Optional[str]:
key = super().keypress(size, key)
if key == "tab":
if (
self.outermost
and self.focus_position == self._last_selectable()
):
self._select_first_selectable()
return None
for i in range(self.focus_position + 1, len(self._contents)):
if self._contents[i][0].selectable():
self.set_focus(i)
if isinstance(self._contents[i][0], SupportsNext):
self._contents[i][0]._select_first_selectable()
return None
elif key == "shift tab":
if (
self.outermost
and self.focus_position == self._first_selectable()
):
self._select_last_selectable()
return None
for i in range(self.focus_position - 1, 0 - 1, -1):
if self._contents[i][0].selectable():
self.set_focus(i)
if isinstance(self._contents[i][0], SupportsNext):
self._contents[i][0]._select_last_selectable()
return None
return key
class NPile(NextMixin, urwid.Pile):
pass
class NColumns(NextMixin, urwid.Columns):
pass
+18 -16
View File
@@ -8,6 +8,7 @@ from pqcli.config import CLASSES, PRIME_STATS, RACES
from pqcli.mechanic import StatsBuilder, create_player, generate_name
from pqcli.ui.custom_button import CustomButton
from pqcli.ui.custom_line_box import CustomLineBox
from pqcli.ui.layout import NColumns, NPile
class StatsBox(CustomLineBox):
@@ -29,17 +30,17 @@ class StatsBox(CustomLineBox):
label_texts = [urwid.Text(f"{stat.value}: ") for stat in PRIME_STATS]
super().__init__(
urwid.ListBox(
[
urwid.Columns(
[urwid.Pile(label_texts), urwid.Pile(value_texts)]
),
urwid.Divider(),
urwid.Columns([urwid.Text("Total: "), self.total_label]),
urwid.Divider(),
roll_button,
unroll_button,
]
urwid.Filler(
NPile(
[
NColumns([NPile(label_texts), NPile(value_texts)]),
urwid.Divider(),
NColumns([urwid.Text("Total: "), self.total_label]),
urwid.Divider(),
roll_button,
unroll_button,
]
)
),
title="Stats",
)
@@ -115,7 +116,7 @@ class ClassBox(CustomLineBox):
self.class_ = user_data
class NewGameView(urwid.Pile):
class NewGameView(NPile):
signals = ["confirm", "cancel"]
def __init__(self) -> None:
@@ -134,7 +135,7 @@ class NewGameView(urwid.Pile):
buttons_box = urwid.Filler(
urwid.Padding(
urwid.Pile(
NPile(
[
CustomButton(
"Sold!", hint="F10", on_press=self.on_confirm_press
@@ -162,14 +163,15 @@ class NewGameView(urwid.Pile):
)
),
),
urwid.Columns(
NColumns(
[
(urwid.WEIGHT, 2, self.race_box),
(urwid.WEIGHT, 2, self.class_box),
urwid.Pile([(13, self.stats_box), buttons_box]),
NPile([(13, self.stats_box), buttons_box]),
]
),
]
],
outermost=True,
)
def generate_random_char_name(self) -> None:
+5 -7
View File
@@ -5,6 +5,7 @@ import urwid
from pqcli.lingo import act_name, to_roman
from pqcli.roster import Roster
from pqcli.ui.custom_button import CustomButton
from pqcli.ui.layout import NColumns, NPile
class RosterView(urwid.Filler):
@@ -39,7 +40,7 @@ class RosterView(urwid.Filler):
)
buttons.append(
urwid.Columns(
NColumns(
[
CustomButton(
label=label,
@@ -73,13 +74,10 @@ class RosterView(urwid.Filler):
super().__init__(
urwid.Padding(
urwid.Pile(
NPile(
[urwid.Padding(logo, width="clip"), urwid.Divider()]
+ buttons
+ [
urwid.Divider(),
urwid.Text("Use arrow keys to move around."),
]
+ buttons,
outermost=True,
),
align="center",
width=logo.pack()[0],
+1 -1
View File
@@ -19,7 +19,7 @@
import typing as T
import urwid
from urwid.widget import BOX, FLOW, FIXED
from urwid.widget import BOX, FIXED, FLOW
# Scroll actions
SCROLL_LINE_UP = "line up"