Merge pull request #316 from CanonicalLtd/mwhudson/stretchy-overlay
add an overlay that has nicer resizing behaviour
This commit is contained in:
commit
ba041718dc
|
@ -20,12 +20,7 @@ configuration.
|
|||
|
||||
"""
|
||||
import logging
|
||||
from urwid import (
|
||||
LineBox,
|
||||
Padding as UrwidPadding,
|
||||
Text,
|
||||
WidgetWrap,
|
||||
)
|
||||
from urwid import Text
|
||||
|
||||
from subiquitycore.ui.buttons import (
|
||||
back_btn,
|
||||
|
@ -36,6 +31,7 @@ from subiquitycore.ui.buttons import (
|
|||
reset_btn,
|
||||
)
|
||||
from subiquitycore.ui.container import Columns, ListBox, Pile
|
||||
from subiquitycore.ui.stretchy import Stretchy
|
||||
from subiquitycore.ui.utils import button_pile, Color, Padding
|
||||
from subiquitycore.view import BaseView
|
||||
|
||||
|
@ -52,22 +48,24 @@ result in the loss of data on the disks selected to be formatted.
|
|||
You will not be able to return to this or a previous screen once \
|
||||
the installation has started.
|
||||
|
||||
Are you sure you want to continue?
|
||||
""")
|
||||
Are you sure you want to continue?""")
|
||||
|
||||
class FilesystemConfirmationView(WidgetWrap):
|
||||
class FilesystemConfirmation(Stretchy):
|
||||
def __init__(self, parent, controller):
|
||||
self.parent = parent
|
||||
self.controller = controller
|
||||
pile = Pile([
|
||||
UrwidPadding(Text(confirmation_text), left=2, right=2),
|
||||
widgets = [
|
||||
Text(confirmation_text),
|
||||
Text(""),
|
||||
button_pile([
|
||||
cancel_btn(_("No"), on_press=self.cancel),
|
||||
danger_btn(_("Continue"), on_press=self.ok)]),
|
||||
Text(""),
|
||||
])
|
||||
lb = LineBox(pile, title=_("Confirm destructive action"))
|
||||
super().__init__(lb)
|
||||
]
|
||||
super().__init__(
|
||||
_("Confirm destructive action"),
|
||||
widgets,
|
||||
stretchy_index=0,
|
||||
focus_index=2)
|
||||
|
||||
def ok(self, sender):
|
||||
self.controller.finish()
|
||||
|
@ -275,4 +273,4 @@ class FilesystemView(BaseView):
|
|||
self.controller.reset()
|
||||
|
||||
def done(self, button):
|
||||
self.show_overlay(FilesystemConfirmationView(self, self.controller), min_width=0)
|
||||
self.show_stretchy_overlay(FilesystemConfirmation(self, self.controller))
|
||||
|
|
|
@ -18,8 +18,6 @@ import logging
|
|||
from urwid import (
|
||||
connect_signal,
|
||||
LineBox,
|
||||
Padding as UrwidPadding,
|
||||
SolidFill,
|
||||
Text,
|
||||
WidgetWrap,
|
||||
)
|
||||
|
@ -31,7 +29,6 @@ from subiquitycore.ui.buttons import (
|
|||
)
|
||||
from subiquitycore.ui.container import (
|
||||
Columns,
|
||||
ListBox,
|
||||
Pile,
|
||||
)
|
||||
from subiquitycore.ui.form import (
|
||||
|
@ -39,6 +36,9 @@ from subiquitycore.ui.form import (
|
|||
Form,
|
||||
)
|
||||
from subiquitycore.ui.selector import Selector, Option
|
||||
from subiquitycore.ui.stretchy import (
|
||||
Stretchy,
|
||||
)
|
||||
from subiquitycore.ui.utils import button_pile, Color, Padding, screen
|
||||
from subiquitycore.view import BaseView
|
||||
|
||||
|
@ -302,7 +302,7 @@ toggle_options = [
|
|||
]
|
||||
|
||||
|
||||
class ToggleQuestion(WidgetWrap):
|
||||
class ToggleQuestion(Stretchy):
|
||||
|
||||
def __init__(self, parent, setting):
|
||||
self.parent = parent
|
||||
|
@ -315,28 +315,24 @@ class ToggleQuestion(WidgetWrap):
|
|||
except AttributeError:
|
||||
pass
|
||||
|
||||
pile = Pile([
|
||||
ListBox([
|
||||
Text(_(toggle_text)),
|
||||
]),
|
||||
(1, SolidFill(" ")),
|
||||
('pack', Padding.center_79(Columns([
|
||||
widgets = [
|
||||
Text(_(toggle_text)),
|
||||
Text(""),
|
||||
Padding.center_79(Columns([
|
||||
('pack', Text(_("Shortcut: "))),
|
||||
Color.string_input(self.selector),
|
||||
]))),
|
||||
(1, SolidFill(" ")),
|
||||
('pack', button_pile([
|
||||
])),
|
||||
Text(""),
|
||||
button_pile([
|
||||
ok_btn(label=_("OK"), on_press=self.ok),
|
||||
cancel_btn(label=_("Cancel"), on_press=self.cancel),
|
||||
])),
|
||||
])
|
||||
pile.focus_position = 4
|
||||
]),
|
||||
]
|
||||
super().__init__(
|
||||
LineBox(
|
||||
UrwidPadding(
|
||||
pile,
|
||||
left=1, right=1),
|
||||
_("Select layout toggle")))
|
||||
_("Select layout toggle"),
|
||||
widgets,
|
||||
stretchy_index=0,
|
||||
focus_index=4)
|
||||
|
||||
def ok(self, sender):
|
||||
self.parent.remove_overlay()
|
||||
|
@ -411,7 +407,7 @@ class KeyboardView(BaseView):
|
|||
setting = KeyboardSetting(layout=layout, variant=variant)
|
||||
new_setting = setting.latinizable()
|
||||
if new_setting != setting:
|
||||
self.show_overlay(ToggleQuestion(self, new_setting), height=('relative', 100))
|
||||
self.show_stretchy_overlay(ToggleQuestion(self, new_setting))
|
||||
return
|
||||
self.really_done(setting)
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
# Copyright 2018 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Custom overlay that only takes the space it needs but can still scroll.
|
||||
|
||||
There are a couple of dialogs in subiquity that basically have the form:
|
||||
|
||||
+------ dialog title --------+
|
||||
| |
|
||||
| potentially quite long |
|
||||
| block of text that might |
|
||||
| take several lines and so |
|
||||
| need to be scrollable |
|
||||
| |
|
||||
| maybe another widget |
|
||||
| |
|
||||
| [ OK ] |
|
||||
| [ Cancel ] |
|
||||
| |
|
||||
+----------------------------+
|
||||
|
||||
The way urwid works makes doing this nicely hard. Simply putting the
|
||||
text in a ListBox "works" but because the listbox is a box widget we
|
||||
can't let it choose its height: either we'll end up having the text
|
||||
scrollable in some situations where there is enough space on the
|
||||
screen to show it all or when the screen is large there will be a
|
||||
massive ugly space between the text and the following widgets.
|
||||
|
||||
Because you can't make a widget that behaves the way we want, this
|
||||
module gives a way of providing a list of widgets to display in a Pile
|
||||
and and nominating one of them to be made scrollable. A title, a list
|
||||
of widgets and index to be scrollable is bundled up into an object
|
||||
called a "stretchy" but is a bad name but at least easily greppable.
|
||||
"""
|
||||
|
||||
|
||||
import urwid
|
||||
|
||||
from subiquitycore.ui.container import ListBox, Pile
|
||||
|
||||
class Stretchy:
|
||||
def __init__(self, title, widgets, stretchy_index, focus_index):
|
||||
"""
|
||||
title: goes in the LineBox
|
||||
widgets: list of widgets to put in the pile
|
||||
stretchy_index: index into widgets of widget to wrap in ListBox
|
||||
focus_index: index into widgets of initial focus
|
||||
"""
|
||||
self.title = title
|
||||
self.widgets = widgets
|
||||
self.stretchy_index = stretchy_index
|
||||
self.focus_index = focus_index
|
||||
|
||||
@property
|
||||
def stretchy_w(self):
|
||||
return self.widgets[self.stretchy_index]
|
||||
|
||||
|
||||
class StretchyOverlay(urwid.Widget):
|
||||
_selectable = True
|
||||
_sizing = frozenset([urwid.BOX])
|
||||
def __init__(self, bottom_w, stretchy):
|
||||
self.bottom_w = bottom_w
|
||||
self.stretchy = stretchy
|
||||
self.listbox = ListBox([stretchy.stretchy_w])
|
||||
def entry(i, w):
|
||||
if i == stretchy.stretchy_index:
|
||||
return ('weight', 1, self.listbox)
|
||||
else:
|
||||
return ('pack', w)
|
||||
# this Filler/Padding/LineBox/Filler/Padding construction
|
||||
# seems ridiculous but it works.
|
||||
self.top_w = urwid.Filler(
|
||||
urwid.Padding(
|
||||
urwid.LineBox(
|
||||
urwid.Filler(
|
||||
urwid.Padding(
|
||||
Pile(
|
||||
[entry(i, w) for (i, w) in enumerate(stretchy.widgets)],
|
||||
focus_item=stretchy.widgets[stretchy.focus_index]),
|
||||
left=2, right=2),
|
||||
top=1, bottom=1, height=('relative', 100)),
|
||||
title=stretchy.title),
|
||||
left=3, right=3),
|
||||
top=1, bottom=1, height=('relative', 100))
|
||||
|
||||
def _top_size(self, size, focus):
|
||||
# Returns the size of the top widget and whether the scollbar will be shown.
|
||||
|
||||
maxcol, maxrow = size # we are a BOX widget
|
||||
outercol = min(maxcol, 80)
|
||||
innercol = outercol - 10 # (3 outer padding, 1 line, 2 inner padding) x 2
|
||||
fixed_rows = 6 # lines at top and bottom and padding
|
||||
|
||||
for i, widget in enumerate(self.stretchy.widgets):
|
||||
if i == self.stretchy.stretchy_index:
|
||||
continue
|
||||
if urwid.FLOW in widget.sizing():
|
||||
rows = widget.rows((innercol,), focus)
|
||||
fixed_rows += rows
|
||||
else:
|
||||
w_size = widget.pack((), focus)
|
||||
fixed_rows += w_size[1]
|
||||
|
||||
if fixed_rows > maxrow:
|
||||
# There's no space for the stretchy widget at all,
|
||||
# probably something will break but well let's defer the
|
||||
# problem for now.
|
||||
return (outercol, size[1]), False
|
||||
|
||||
stretchy_ideal_rows = self.stretchy.stretchy_w.rows((innercol,), focus)
|
||||
|
||||
if maxrow - fixed_rows >= stretchy_ideal_rows:
|
||||
return (outercol, stretchy_ideal_rows + fixed_rows), False
|
||||
else:
|
||||
return (outercol, size[1]), True
|
||||
|
||||
def keypress(self, size, key):
|
||||
top_size, scrollbar_visible = self._top_size(size, True)
|
||||
self.listbox._selectable = scrollbar_visible
|
||||
return self.top_w.keypress(top_size, key)
|
||||
|
||||
def render(self, size, focus):
|
||||
bottom_c = self.bottom_w.render(size, False)
|
||||
if not bottom_c.cols() or not bottom_c.rows():
|
||||
return urwid.CompositeCanvas(bottom_c)
|
||||
|
||||
top_size, _ = self._top_size(size, focus)
|
||||
top_c = self.top_w.render(top_size, focus)
|
||||
top_c = urwid.CompositeCanvas(top_c)
|
||||
left = (size[0] - top_size[0]) // 2
|
||||
top = (size[1] - top_size[1]) // 2
|
||||
return urwid.CanvasOverlay(top_c, bottom_c, left, top)
|
|
@ -18,17 +18,14 @@
|
|||
Contains some default key navigations
|
||||
"""
|
||||
|
||||
from urwid import Columns, Overlay, Pile, SolidFill, Text, WidgetWrap
|
||||
from subiquitycore.ui.stretchy import StretchyOverlay
|
||||
|
||||
from urwid import Columns, Overlay, Pile, Text, WidgetWrap
|
||||
|
||||
|
||||
class BaseView(WidgetWrap):
|
||||
|
||||
def __init__(self, w):
|
||||
self.orig_w = None
|
||||
super().__init__(w)
|
||||
|
||||
def show_overlay(self, overlay_widget, **kw):
|
||||
self.orig_w = self._w
|
||||
args = dict(
|
||||
align='center',
|
||||
width=('relative', 60),
|
||||
|
@ -42,26 +39,22 @@ class BaseView(WidgetWrap):
|
|||
if isinstance(kw['width'], int):
|
||||
kw['width'] += 2*PADDING
|
||||
args.update(kw)
|
||||
if 'height' in kw:
|
||||
f = SolidFill(" ")
|
||||
p = 1
|
||||
else:
|
||||
f = Text("")
|
||||
p = 'pack'
|
||||
top = Pile([
|
||||
(p, f),
|
||||
('pack', Text("")),
|
||||
Columns([
|
||||
(PADDING, f),
|
||||
(PADDING, Text("")),
|
||||
overlay_widget,
|
||||
(PADDING, f)
|
||||
(PADDING, Text(""))
|
||||
]),
|
||||
(p, f),
|
||||
('pack', Text("")),
|
||||
])
|
||||
self._w = Overlay(top_w=top, bottom_w=self._w, **args)
|
||||
|
||||
def show_stretchy_overlay(self, stretchy):
|
||||
self._w = StretchyOverlay(self._w, stretchy)
|
||||
|
||||
def remove_overlay(self):
|
||||
self._w = self.orig_w
|
||||
self.orig_w = None
|
||||
self._w = self._w.bottom_w
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue