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

widget to display multi-line text #766

Closed
BrendanSimon opened this issue Dec 25, 2019 · 15 comments
Closed

widget to display multi-line text #766

BrendanSimon opened this issue Dec 25, 2019 · 15 comments
Labels
enhancement New features, or improvements to existing features.

Comments

@BrendanSimon
Copy link
Contributor

Is there a Toga widget to display multiline text?

The closest I can see is using a MultilineTextInput with readonly=True.

At the moment I'm creating multiple labels within a box, which gets tedious, although I could pass a string with embedded newline chars to a function to do it programatically.

@freakboy3742 freakboy3742 added enhancement New features, or improvements to existing features. up-for-grabs labels Dec 25, 2019
@freakboy3742
Copy link
Member

I'm guessing from the readonly=True detail you mean multiline static text (i.e., non-editable).

It sounds like a new MultilineLabel widget (or, at least, a new word-wrap option on the Label widget) is called for.

If anyone is interested in hacking around on this, Dan Yeaw's talk from PyCon 2019 may be instructive.

@BrendanSimon
Copy link
Contributor Author

Yes, static text (which seems to be called Label in Toga).

Support for the following (extend existing Label widget or new widget if necessary):

  • word wrap
  • newline

How does one currently display a long line/paragraph(s) of text?

@freakboy3742
Copy link
Member

Currently? One doesn't :-)

The current Label widget assumes it's a simple text annotation of an input widget. There are side effects of allowing a label to do something like word wrap (e.g., consider the box algorithm - should the box try to be the size of the label, or should the label wrap to be the size of the available box?). That's why a new widget is required.

@BrendanSimon
Copy link
Contributor Author

If a new widget is required then I'm ok with that, especially if it's simpler to use and maintain, etc.

But, (thinking out loud), couldn't it depend on attributes of the widget (with defaults being the same as Label's current behvaiour)? i.e. Label with extra settable attributes to define behaviour. A new widget could be a derived class (or a function/decorator?) with to attributes set to suitable values for multiline static text?

e.g. wrapping would occur when some "max_width" limit was reached. If "max_width" was not set (None) then no word wrapping would apply and the widgets width would be the maximum width of all lines.

\n would indicate a newline and the height of the widget would increase. If the a "max_height" attribute is specified then the widget height would be capped at that.

What to do with text "off-screen"? Truncate or allow to scroll if a "row_scroll" or "column_scroll" attribute is set.

@BrendanSimon
Copy link
Contributor Author

I guess there is no "rich text" widget either. e.g. a widget that can display text with some chars/words in bold and/or italics and/or other styles and/or coloured, etc.

What would be the proposed widget for that?

Would the multiline widget morph into that too?

I guess some kind of web widget could do it, but is that fit the Toga way?

Can unicode deal with all that? I found https://www.babelstone.co.uk/Unicode/text.html which shows that it can, but the encoded text for styled chars/words is not easy to author - e.g. can't just insert a start/end style marker within text.

@freakboy3742
Copy link
Member

To clarify - an extra attribute on the existing Label widget (is multiline=True) may be a viable option for implementing multiline text.

In the case of TextInput vs MultilineTextInput, it wasn't possible to easily accommodate both behaviors in a single widget because the MultilineTextInput requires the use of an internal scrollbox. That particular constraint won't exist for a multiline Label, but I can easily imagine that the complexities of managing multiline text might mean that the implementation of Label ends up being covered with block of "if self.multiline" logic - at which point, you have to question whether you actually have 2 different widgets.

Subclassing may also be an option. There will, after all, be some common behavior (e.g., setting the text); so, having MultilineLabel be a subclass of Label that overrides significant pieces of logic may also be a viable option.

In short - we're at the point where some exploratory work is needed to work out what is viable.

As for a "rich text" widget - again, it depends what you mean. If you mean static text, then I imagine MultilineLabel should encompass some of that; or, in one interpretation, we already have that capability with WebView. If you mean a rich text editor - well, that's a much bigger project. A rich text editor (and more specifically, a code editor) is definitely on my wish list; but it's also a big task. In the past, I've hacked together a rough demo of such a widget using the WebView with a rich content editor embedded in it - and while that is sufficient to get something working, it's not my ideal end solution. However, as a stop gap measure... maybe it's enough?

I'm not sure what you've got in mind when you refer to "Unicode" as an option here. Unicode is a text encoding scheme, not a markup mechanism. There are some limited styling hacks you can use based on switching code planes; but they're not really intended for styling, and they're entirely dependent on the underlying font having representations for code points on the planes you use (i.e., there's no guarantee that the symbols on the Fraktur math codeplane will actually appear consistently between fonts, between platforms, or even appear at all).

@BrendanSimon
Copy link
Contributor Author

I have a box layout problem on iOS when using my MutilineLabel function. It seems to layout ok on macOS (apart from some missing text on the label itself). i.e. the width of the box looks ok on macOS but not on iOS.

Here is trimmed down code that I'm using.

#!/usr/bin/env python

from __future__ import annotations
from typing import ( Optional, List )

import toga
from toga.style import Pack
from toga.style.pack import ( COLUMN, ROW, CENTER )

#!============================================================================

def MultilineLabel( text : str,
                    box_style : Pack = None,
                    label_style : Pack = None,
                  ) -> toga.Box :
    """
    Return a Toga Box of Toga Label widgets to mimic a multiline label widget.
    """
    
    box = toga.Box( id = None,
                    style = box_style,
                    children = [ toga.Label( t, style=label_style ) for t in text.split() ]
                    )
    
    return box

#!============================================================================

class SGGC_Live( toga.App ) :

    def startup( self ) :

        #! Create the main window
        self.main_window = toga.MainWindow( title=self.name )

        #!
        #! populate boxes with widgets
        #!

        head_box = toga.Box( id = 'head_box',
                             style = Pack( direction=COLUMN, padding=10, background_color='teal' ),
                             children = [
                                toga.Label( 'SGGC 2019', style=Pack( text_align=CENTER ) ),
                                toga.Label( 'South Gippsland Golf Inc', style=Pack( text_align=CENTER ) ),
                                ],
                             ) 

        days_box = toga.Box( id = 'days_box',
                             style = Pack( direction=ROW, padding=10, background_color='teal' ),
                             children = [
                                toga.Label( 'Day1', style=Pack( flex=0, text_align=CENTER ) ),

                                # toga.Label( 'Day2', style=Pack( flex=0, text_align=CENTER ) ),
                                MultilineLabel( "Sat\n15th\nFeb",
                                                box_style = Pack( direction=COLUMN, flex=0, background_color='yellow' ),
                                                label_style = Pack( text_align=CENTER, flex=0 )
                                                ),

                                toga.Label( 'DayX', style=Pack( flex=0, text_align=CENTER ) ),
                                ]
                             ) 

        body_box = toga.Box( id = 'body_box',
                             style = Pack( direction=COLUMN, flex=1, padding=10 ),
                             children = [
                                toga.Box( style=Pack( flex=1 ) ),
                                toga.Label( 'Body', style=Pack( text_align=CENTER, flex=1 ) ),
                                toga.Label( 'Body', style=Pack( text_align=CENTER, flex=1 ) ),
                                toga.Label( 'Body', style=Pack( text_align=CENTER, flex=1 ) ),
                                toga.Box( style=Pack( flex=1 ) ),
                                ],
                             ) 


        foot_box = toga.Box( id = 'foot_box',
                             style = Pack( direction=ROW, flex=0, padding=10, background_color='teal' ),
                             children = [
                                toga.Label( 'F1', style=Pack( flex=1, text_align=CENTER ) ),
                                toga.Label( 'F2', style=Pack( flex=1, text_align=CENTER ) ),
                                toga.Label( 'F3', style=Pack( flex=1, text_align=CENTER ) ),
                                ],
                           )



        main_box = toga.Box( id = 'main_box',
                             style = Pack( direction=COLUMN, flex=0 ),
                             children = [ head_box, days_box, body_box, foot_box ],
                             )

        self.main_window.content = main_box

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

#!============================================================================

def main() :
    return SGGC_Live( 'SGGC Live', 'au.com.etrix.sggc_live' )

If I uncomment the line to show toga label Day2, and comment out the MultilineLabel call, I get the following screenshots.

toga-day2-macos-ok

toga-day2-ios-ok

If I comment out the toga label Day2, and uncomment the MultilineLabel call (as code above), I get:

toga-multilinelabel-macos-ok

toga-multilinelabel-ios-too-wide

Is this a Toga issue or have I missed something? Even if I have made a mistake, I would expect the same result on both platforms.

@BrendanSimon
Copy link
Contributor Author

If I change the box style of the MultilineLable widget to ROW, then I get the same layout on both platforms.

box_style = Pack( direction=ROW, flex=0, background_color='yellow' ),

but I want the COLUMN layout for multiple lines - but at least it's data point for debugging.

@BrendanSimon
Copy link
Contributor Author

BrendanSimon commented Dec 30, 2019

If I set the box style width=1 (with direction=COLUMN) then I get what I want (not for my app, but consistent with what I specify). Note: width=0 didn't change anything.

I'm not sure if this is the correct way of specifying this or whether this is a "workaround".

box_style = Pack( direction=COLUMN, flex=0, width=1, background_color='yellow' ),

toga-multilinelabel-width1-ios-ok

@BrendanSimon
Copy link
Contributor Author

width=-1 also seems to work ok - which might be a better choice that 1 - to indicate a special value, rather that a very small width that might be considered a real intended width ??

@aitoehigie
Copy link

From the documentation, it seems MultilineTextInput is not supported on Android? Are there any alternatives?

@freakboy3742
Copy link
Member

@aitoehigie At present, no. The widget set on Android is limited, as the platform itself has only recently been rebuilt. We're in the process of filling in the gaps.

@mhsmith mhsmith mentioned this issue Jun 5, 2022
4 tasks
@mhsmith
Copy link
Member

mhsmith commented Jun 14, 2022

For future reference: #1498 fixed newlines in labels on all platforms except iOS, which is tracked by #1501.

Automatic word wrapping is a much harder issue, because as Russell said on Discord:

it’s ill posed from a layout perspective. A single line of text has a defined height and width. Multiline gets into questions of how line breaks work - and when a layout will choose “narrow but tall” over “wide but short”

The most common requirement would be "width determined by other constraints, then as tall as necessary to fit the text". But I’m not sure if this can be accommodated by the current layout algorithm, because as I understand it, each widget declares its minimum width and height simultaneously, and then its parent container assigns actual width and height simultaneously. In order for one to depend on the other, we’d need two phases. For example, a ROW box would first determine the widths of its children, and then ask each child what its height would be for the assigned width.

I'm sure we can take some guidance from CSS here.

@freakboy3742
Copy link
Member

CSS is definitely the standard to follow here - it's pretty much the raison d'être of CSS. Pack definitely isn't up to the task as-is, because it doesn't have any line break/reflow mechanism.

I don't know how much effort is worth putting into Pack vs improving Colosseum; I suspect it will be more worthwhile to keep Pack simple and make Colosseum the focus of complex layout. We will also likely need to lean on platform specific implementations of text reflow to make multiline labels work.

@freakboy3742
Copy link
Member

I'm going to close this; we now support newlines in labels, which allows for multiline labels. A full reflow model for text would require a radical redesign of Pack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New features, or improvements to existing features.
Projects
None yet
Development

No branches or pull requests

4 participants