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
|
import logging
|
||||||
from urwid import (
|
from urwid import Text
|
||||||
LineBox,
|
|
||||||
Padding as UrwidPadding,
|
|
||||||
Text,
|
|
||||||
WidgetWrap,
|
|
||||||
)
|
|
||||||
|
|
||||||
from subiquitycore.ui.buttons import (
|
from subiquitycore.ui.buttons import (
|
||||||
back_btn,
|
back_btn,
|
||||||
|
@ -36,6 +31,7 @@ from subiquitycore.ui.buttons import (
|
||||||
reset_btn,
|
reset_btn,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.container import Columns, ListBox, Pile
|
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.ui.utils import button_pile, Color, Padding
|
||||||
from subiquitycore.view import BaseView
|
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 \
|
You will not be able to return to this or a previous screen once \
|
||||||
the installation has started.
|
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):
|
def __init__(self, parent, controller):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
pile = Pile([
|
widgets = [
|
||||||
UrwidPadding(Text(confirmation_text), left=2, right=2),
|
Text(confirmation_text),
|
||||||
|
Text(""),
|
||||||
button_pile([
|
button_pile([
|
||||||
cancel_btn(_("No"), on_press=self.cancel),
|
cancel_btn(_("No"), on_press=self.cancel),
|
||||||
danger_btn(_("Continue"), on_press=self.ok)]),
|
danger_btn(_("Continue"), on_press=self.ok)]),
|
||||||
Text(""),
|
]
|
||||||
])
|
super().__init__(
|
||||||
lb = LineBox(pile, title=_("Confirm destructive action"))
|
_("Confirm destructive action"),
|
||||||
super().__init__(lb)
|
widgets,
|
||||||
|
stretchy_index=0,
|
||||||
|
focus_index=2)
|
||||||
|
|
||||||
def ok(self, sender):
|
def ok(self, sender):
|
||||||
self.controller.finish()
|
self.controller.finish()
|
||||||
|
@ -275,4 +273,4 @@ class FilesystemView(BaseView):
|
||||||
self.controller.reset()
|
self.controller.reset()
|
||||||
|
|
||||||
def done(self, button):
|
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 (
|
from urwid import (
|
||||||
connect_signal,
|
connect_signal,
|
||||||
LineBox,
|
LineBox,
|
||||||
Padding as UrwidPadding,
|
|
||||||
SolidFill,
|
|
||||||
Text,
|
Text,
|
||||||
WidgetWrap,
|
WidgetWrap,
|
||||||
)
|
)
|
||||||
|
@ -31,7 +29,6 @@ from subiquitycore.ui.buttons import (
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.container import (
|
from subiquitycore.ui.container import (
|
||||||
Columns,
|
Columns,
|
||||||
ListBox,
|
|
||||||
Pile,
|
Pile,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.form import (
|
from subiquitycore.ui.form import (
|
||||||
|
@ -39,6 +36,9 @@ from subiquitycore.ui.form import (
|
||||||
Form,
|
Form,
|
||||||
)
|
)
|
||||||
from subiquitycore.ui.selector import Selector, Option
|
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.ui.utils import button_pile, Color, Padding, screen
|
||||||
from subiquitycore.view import BaseView
|
from subiquitycore.view import BaseView
|
||||||
|
|
||||||
|
@ -302,7 +302,7 @@ toggle_options = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class ToggleQuestion(WidgetWrap):
|
class ToggleQuestion(Stretchy):
|
||||||
|
|
||||||
def __init__(self, parent, setting):
|
def __init__(self, parent, setting):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
@ -315,28 +315,24 @@ class ToggleQuestion(WidgetWrap):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
pile = Pile([
|
widgets = [
|
||||||
ListBox([
|
Text(_(toggle_text)),
|
||||||
Text(_(toggle_text)),
|
Text(""),
|
||||||
]),
|
Padding.center_79(Columns([
|
||||||
(1, SolidFill(" ")),
|
|
||||||
('pack', Padding.center_79(Columns([
|
|
||||||
('pack', Text(_("Shortcut: "))),
|
('pack', Text(_("Shortcut: "))),
|
||||||
Color.string_input(self.selector),
|
Color.string_input(self.selector),
|
||||||
]))),
|
])),
|
||||||
(1, SolidFill(" ")),
|
Text(""),
|
||||||
('pack', button_pile([
|
button_pile([
|
||||||
ok_btn(label=_("OK"), on_press=self.ok),
|
ok_btn(label=_("OK"), on_press=self.ok),
|
||||||
cancel_btn(label=_("Cancel"), on_press=self.cancel),
|
cancel_btn(label=_("Cancel"), on_press=self.cancel),
|
||||||
])),
|
]),
|
||||||
])
|
]
|
||||||
pile.focus_position = 4
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
LineBox(
|
_("Select layout toggle"),
|
||||||
UrwidPadding(
|
widgets,
|
||||||
pile,
|
stretchy_index=0,
|
||||||
left=1, right=1),
|
focus_index=4)
|
||||||
_("Select layout toggle")))
|
|
||||||
|
|
||||||
def ok(self, sender):
|
def ok(self, sender):
|
||||||
self.parent.remove_overlay()
|
self.parent.remove_overlay()
|
||||||
|
@ -411,7 +407,7 @@ class KeyboardView(BaseView):
|
||||||
setting = KeyboardSetting(layout=layout, variant=variant)
|
setting = KeyboardSetting(layout=layout, variant=variant)
|
||||||
new_setting = setting.latinizable()
|
new_setting = setting.latinizable()
|
||||||
if new_setting != setting:
|
if new_setting != setting:
|
||||||
self.show_overlay(ToggleQuestion(self, new_setting), height=('relative', 100))
|
self.show_stretchy_overlay(ToggleQuestion(self, new_setting))
|
||||||
return
|
return
|
||||||
self.really_done(setting)
|
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
|
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):
|
class BaseView(WidgetWrap):
|
||||||
|
|
||||||
def __init__(self, w):
|
|
||||||
self.orig_w = None
|
|
||||||
super().__init__(w)
|
|
||||||
|
|
||||||
def show_overlay(self, overlay_widget, **kw):
|
def show_overlay(self, overlay_widget, **kw):
|
||||||
self.orig_w = self._w
|
|
||||||
args = dict(
|
args = dict(
|
||||||
align='center',
|
align='center',
|
||||||
width=('relative', 60),
|
width=('relative', 60),
|
||||||
|
@ -42,26 +39,22 @@ class BaseView(WidgetWrap):
|
||||||
if isinstance(kw['width'], int):
|
if isinstance(kw['width'], int):
|
||||||
kw['width'] += 2*PADDING
|
kw['width'] += 2*PADDING
|
||||||
args.update(kw)
|
args.update(kw)
|
||||||
if 'height' in kw:
|
|
||||||
f = SolidFill(" ")
|
|
||||||
p = 1
|
|
||||||
else:
|
|
||||||
f = Text("")
|
|
||||||
p = 'pack'
|
|
||||||
top = Pile([
|
top = Pile([
|
||||||
(p, f),
|
('pack', Text("")),
|
||||||
Columns([
|
Columns([
|
||||||
(PADDING, f),
|
(PADDING, Text("")),
|
||||||
overlay_widget,
|
overlay_widget,
|
||||||
(PADDING, f)
|
(PADDING, Text(""))
|
||||||
]),
|
]),
|
||||||
(p, f),
|
('pack', Text("")),
|
||||||
])
|
])
|
||||||
self._w = Overlay(top_w=top, bottom_w=self._w, **args)
|
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):
|
def remove_overlay(self):
|
||||||
self._w = self.orig_w
|
self._w = self._w.bottom_w
|
||||||
self.orig_w = None
|
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue