Skip to content
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

Draft: Add text wrap support for Label widget on Winform and GTK #2437

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2428.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Issue with `visibility` on GTK and `display` on all platform.
35 changes: 32 additions & 3 deletions core/src/toga/style/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def _debug(self, *args): # pragma: no cover
@property
def _hidden(self):
"Does this style declaration define a object that should be hidden"
return self.visibility == HIDDEN
return self.visibility == HIDDEN or self.display == NONE

def apply(self, prop, value):
if self._applicator:
Expand All @@ -104,8 +104,8 @@ def apply(self, prop, value):
self._applicator.set_color(value)
elif prop == "background_color":
self._applicator.set_background_color(value)
elif prop == "visibility":
self._applicator.set_hidden(value == HIDDEN)
elif prop == "visibility" or prop == "display":
self._applicator.set_hidden(self._hidden)
elif prop in (
"font_family",
"font_size",
Expand Down Expand Up @@ -236,6 +236,11 @@ def _layout_node(
else:
width = available_width
height = available_height
if hasattr(node._impl, 'compute_size'):
# To support text wraping, when fixing the width, we need to recompute the height.
width, height = node._impl.compute_size(width, height)
min_height = height

# self._debug(f"NO CHILDREN {min_width=} {width=} {min_height=} {height=}")

# If an explicit width/height was given, that specification
Expand Down Expand Up @@ -275,6 +280,9 @@ def _layout_row_children(
min_width = 0
remaining_width = available_width
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 1 {child}")
if child.style.width != NONE:
# self._debug(f"- fixed width {child.style.width}")
Expand Down Expand Up @@ -404,6 +412,9 @@ def _layout_row_children(
# Pass 2: Lay out children with an intrinsic flexible width,
# or no width specification at all.
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 2 {child}")
if child.style.width != NONE:
# self._debug("- already laid out (explicit width)")
Expand Down Expand Up @@ -489,6 +500,9 @@ def _layout_row_children(
height = 0
min_height = 0
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 3: {child} AT HORIZONTAL {offset=}")
if node.style.text_direction is RTL:
# self._debug("- RTL")
Expand Down Expand Up @@ -522,6 +536,9 @@ def _layout_row_children(

# Pass 4: set vertical position of each child.
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 4: {child}")
extra = height - (
child.layout.content_height
Expand Down Expand Up @@ -559,6 +576,9 @@ def _layout_column_children(
min_height = 0
remaining_height = available_height
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 1 {child}")
if child.style.height != NONE:
# self._debug(f"- fixed height {child.style.height}")
Expand Down Expand Up @@ -689,6 +709,9 @@ def _layout_column_children(
# Pass 2: Lay out children with an intrinsic flexible height,
# or no height specification at all.
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 2 {child}")
if child.style.height != NONE:
# self._debug("- already laid out (explicit height)")
Expand Down Expand Up @@ -775,6 +798,9 @@ def _layout_column_children(
width = 0
min_width = 0
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 3: {child} AT VERTICAL OFFSET {offset}")
offset += child.style.padding_top
child.layout.content_top = offset
Expand All @@ -800,6 +826,9 @@ def _layout_column_children(

# Pass 4: set horizontal position of each child.
for child in node.children:
# Skip calculation if widget is not displayed
if child.style.display == NONE:
continue
# self._debug(f"PASS 4: {child}")
extra = width - (
child.layout.content_width
Expand Down
11 changes: 11 additions & 0 deletions core/src/toga/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ def __init__(
text: str,
id=None,
style=None,
wrap=False,
):
"""Create a new text label.

Expand All @@ -23,6 +24,7 @@ def __init__(
self._impl = self.factory.Label(interface=self)

self.text = text
self.wrap = wrap

def focus(self):
"No-op; Label cannot accept input focus"
Expand All @@ -47,3 +49,12 @@ def text(self, value):

self._impl.set_text(text)
self.refresh()

@property
def wrap(self) -> str:
return self._impl.get_wrap()

@text.setter
def wrap(self, value):
self._impl.set_wrap(bool(value))
self.refresh()
5 changes: 1 addition & 4 deletions gtk/src/toga_gtk/widgets/activityindicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@ def stop(self):
self.native.stop()

def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = width[0]
self.interface.intrinsic.height = height[0]
17 changes: 12 additions & 5 deletions gtk/src/toga_gtk/widgets/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def container(self, container):
# setting container, adding self to container.native
self._container = container
self._container.add(self.native)
self.native.show_all()

for child in self.interface.children:
child._impl.container = container
Expand All @@ -84,6 +83,17 @@ def get_tab_index(self):
def set_tab_index(self, tab_index):
self.interface.factory.not_implemented("Widget.set_tab_index()")

def _get_preferred_size(self, native):
# Utility function to get the preferred size of widget regardless of it's visibility.
visible = native.get_visible()
if not visible:
native.set_visible(True)
# print("REHINT", self, native.get_preferred_width(), native.get_preferred_height())
min_size, nat_size = native.get_preferred_size()
if not visible:
native.set_visible(visible)
return (min_size.width, nat_size.width),(min_size.height, nat_size.height)

######################################################################
# CSS tools
######################################################################
Expand Down Expand Up @@ -185,9 +195,6 @@ def refresh(self):

def rehint(self):
# Perform the actual GTK rehint.
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = at_least(width[0])
self.interface.intrinsic.height = at_least(height[0])
5 changes: 1 addition & 4 deletions gtk/src/toga_gtk/widgets/button.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,7 @@ def set_background_color(self, color):
super().set_background_color(color)

def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = at_least(width[0])
self.interface.intrinsic.height = height[1]

Expand Down
1 change: 0 additions & 1 deletion gtk/src/toga_gtk/widgets/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,6 @@ def get_image_data(self):

# Rehint
def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
# width = self.native.get_allocation().width
# height = self.native.get_allocation().height
width = self.interface._MIN_WIDTH
Expand Down
4 changes: 1 addition & 3 deletions gtk/src/toga_gtk/widgets/divider.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ def create(self):
self.native = Gtk.Separator()

def rehint(self):
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
if self.get_direction() == self.interface.VERTICAL:
self.interface.intrinsic.width = width[0]
self.interface.intrinsic.height = at_least(height[1])
Expand Down
26 changes: 19 additions & 7 deletions gtk/src/toga_gtk/widgets/label.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,26 @@ def get_text(self):

def set_text(self, value):
self.native.set_text(value)

def get_wrap(self):
self.native.get_line_wrap()

def set_wrap(self, wrap):
self.native.set_line_wrap(wrap)

def rehint(self):
# print("REHINT", self,
# self.native.get_preferred_width(), self.native.get_preferred_height(),
# getattr(self, '_fixed_height', False), getattr(self, '_fixed_width', False)
# )
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = at_least(width[0])
self.interface.intrinsic.height = height[1]

def compute_size(self, width, height):

if width and self.native.get_line_wrap():
# If wrapping is enabled, calculate prefered height with fixed width
req_width, req_height = self.native.get_size_request()
self.native.set_size_request(width, -1)
_unused, height = self._get_preferred_size(self.native)
self.native.set_size_request(req_width, req_height)
return width, height[0]

return width, height
4 changes: 1 addition & 3 deletions gtk/src/toga_gtk/widgets/numberinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ def set_alignment(self, value):
self.native.set_alignment(xalign)

def rehint(self):
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = at_least(
max(self.interface._MIN_WIDTH, width[1])
)
Expand Down
5 changes: 1 addition & 4 deletions gtk/src/toga_gtk/widgets/progressbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,6 @@ def stop(self):
self._stop_indeterminate()

def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = at_least(width[0])
self.interface.intrinsic.height = height[0]
3 changes: 1 addition & 2 deletions gtk/src/toga_gtk/widgets/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ def get_selected_index(self):
return index

def rehint(self):
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()
width, height = self._get_preferred_size(self.native)

# FIXME: 2023-05-31 This will always provide a size that is big enough,
# but sometimes it will be *too* big. For example, if you set the font size
Expand Down
3 changes: 1 addition & 2 deletions gtk/src/toga_gtk/widgets/slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ def get_tick_count(self):
return self.tick_count

def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
height = self.native.get_preferred_height()
width, height = self._get_preferred_size(self.native)

# Set intrinsic width to at least the minimum width
self.interface.intrinsic.width = at_least(self.interface._MIN_WIDTH)
Expand Down
8 changes: 2 additions & 6 deletions gtk/src/toga_gtk/widgets/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,8 @@ def set_font(self, font):
self.apply_css("font", get_font_css(font), native=self.native_label)

def rehint(self):
# print("REHINT", self, self.native.get_preferred_width(), self.native.get_preferred_height())
label_width = self.native_label.get_preferred_width()
label_height = self.native_label.get_preferred_height()

switch_width = self.native_switch.get_preferred_width()
switch_height = self.native_switch.get_preferred_height()
label_width, label_height = self._get_preferred_size(native=self.native_label)
switch_width, switch_height = self._get_preferred_size(native=self.native_label)

# Set intrinsic width to at least the minimum width
self.interface.intrinsic.width = at_least(
Expand Down
8 changes: 1 addition & 7 deletions gtk/src/toga_gtk/widgets/textinput.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,7 @@ def set_value(self, value):
self.native.set_text(value)

def rehint(self):
# print("REHINT", self,
# self._impl.get_preferred_width(), self._impl.get_preferred_height(),
# getattr(self, '_fixed_height', False), getattr(self, '_fixed_width', False)
# )
width = self.native.get_preferred_width()
height = self.native.get_preferred_height()

width, height = self._get_preferred_size(self.native)
self.interface.intrinsic.width = at_least(
max(self.interface._MIN_WIDTH, width[1])
)
Expand Down
5 changes: 4 additions & 1 deletion gtk/src/toga_gtk/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ def set_app(self, app):
app.native.add_window(self.native)

def show(self):
self.native.show_all()
# Avoid calling show_all() as it change the visibility of all children.
self.native.show()
self.layout.show()
self.container.show()

######################################################################
# Window content and resources
Expand Down
44 changes: 44 additions & 0 deletions temp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import toga
from toga.constants import ROW
from toga.style.pack import Pack
import toga.colors
import sys

IS_LINUX = sys.platform in ["linux", "linux2"]

class TestApp(toga.App):
def startup(self):
# Re-enable pydev debugging in this thread.
try:
import pydevd;
pydevd.settrace()
except ImportError:
pass
self.main_window = toga.MainWindow(size=(300, 150))

style = Pack(padding_top=24)
substyle = Pack(padding_right=12, padding_left=12, flex=1)

# Add the content on the main window
self.main_window.content = toga.Box(
children=[
toga.Button(text="Toggle"),
toga.Label(text="This is a very long line of text to test the wrapping feature in GTK.", style=Pack(flex=1,background_color=toga.colors.BLUE), wrap=1),
toga.TextInput(placeholder="Footer"),
],
style=Pack(direction=ROW, padding=24),
)

# Show the main window
self.main_window.show()

def main():
# Application class
# App name and namespace
app = TestApp("Test", "org.beeware.toga.examples.test")
return app


if __name__ == "__main__":
app = main()
app.main_loop()
Loading
Loading