-
-
Notifications
You must be signed in to change notification settings - Fork 670
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
LinkLabel Proposal #801
Comments
Thanks for the suggestion! I can definitely agree with your use case - a simple hyperlink in a GUI is a common enough element that it makes sense to support it. The research you've done on the Winforms backend is also really helpful. The only questions I would have are about implementation. GTK provides a LinkButton, which has almost exactly the same API that Winforms provides. However, it also provides support for HTML and links in a standard label (see the "Links" section of this page). macOS, iOS, and Android provide similar capabilities, allowing rich text markup in labels, and (with some configuration), hyperlinks. So - the question for Toga becomes which API to support? LinkLabel is the easy approach for Toga to implement. However, adding rich text support to the base Label would, I suspect, result in an easier to manage API for end users - it's one less widget to know about, it would allow for multiple links in a single label, as well as other potential markup; and there would be no need to manage the layout issues associated with including a link in the label text. This is especially relevant if something like #766 is added. However, that somewhat hinges on whether "rich text" approach is possible at all for Winforms. Are you able to dig around the Winforms API and see if there's any options? |
Hey, I'll try to find some time for Winforms research this weekend |
Hey. That will be rather short check than comprehensive research with examples. I hope it helps. Toga APIFirst of all I'm not sure how rich text API we want to expose. GTK markup you've mentioned:
those: are structs that just tells about 2 information:
Maybe its good idea to use in toga as very "granular"? I don't know, to low knowledge, maybe its an overkill.
toga.HTMLLabel('<b>Hello</b> <a href="www.wikipedia.org">World</a>')
my_super_label = toga.Label('Hello', font_size=400) + toga.Label('World', link="www.wikipedia.org")) A bit more verbose and annoying but maybe more simple to code maybe?
toga.Label("{} {}".format(
toga.Label("Hello"),
toga.Label("World", link="www.wikipedia.org")
)) WinformsIn Winforms there is RichTextBox
HTML to RTF Converters: Python RTF Generator: There is also html-rendering library in C# that supports Winforms. This one looks like mature project. Some other ideas/discussion (but mostly RichTextBox): |
Thanks for that research. A few comments:
|
I see. I've tried to adjust RichTextBox for this purpose but did't get it. Scrollbars can be disabled but not scrolling property (if label is longer than available space) class WinformsRichLabel(WinFormsLabel):
def create(self):
self.native = WinForms.RichTextBox()
self.native.set_ScrollBars(0)
self.native.set_BorderStyle(0)
self.native.set_ReadOnly(True)
# self.native.set_WordWrap(False) # forces single line; as side effect, text can be scrolled by selecting (see video)
self.native.set_Multiline(False) # similar effect to wordWrap(False) What difference?
self.native.set_TabStop(False) # disable control focusing using tab navigation
# self.native.set_CanSelect(False) # there is only getter! :(
# self.native.set_Enabled(False) # prevents selection and scrolling effect, but disables all events and text is gray out
class Label(toga.Label):
def __init__(self, text, link=None, id=None, style=None, factory=None):
toga.Widget.__init__(self, id=id, style=style, factory=factory)
self._impl = WinformsRichLabel(interface=self)
# (...) and then after initialization with default sizes...
lbl_style = Pack(font_size=10, text_align="center")
link_label = Label("ve vwer y long asdf asdf asdf asdf aief wejf asldf jaweif asldfj aweif lasdkfjweif jlasfj awei", style=lbl_style)
print('TextLenght:', link_label._impl.native.TextLength)
size = link_label._impl.native.get_MaximumSize()
size.set_Width(100) # arbitral value. TextLength property could be used if we could translate lenght of text to pixels...
link_label._impl.native.set_MaximumSize(size) # or just set_PreferredSize(?) https://i.gyazo.com/b31babdf56b3c8048e4675aaf16303fe.mp4 So unsolved problems are:
Maybe there is answer here: https://github.com/ArthurHub/HTML-Renderer/blob/master/Source/HtmlRenderer.WinForms/HtmlLabel.cs |
That's what I was afraid of - if we can't get the size of the underlying text, then we can't use the size of the text in layout calculations. The HTMLLabel widget looks interesting. If I'm reading that right, it's going back to basics - literally drawing all the component text. That seems slighly overkill under the circumstances, though. In which case, having the Windows implementation be "concatenation of Winforms.Label" (and Winforms.LinkLabel, as needed) might be the easiest path forward. As an aside/simplification, you should find that you can assign attributes directly, rather than invoking |
I have used a hacked-together version of a class RichLabel(Widget):
"""A multiline text view with html support. Rehint is only a hack for now.
Using the layout manager of NSTextView does not work well since it generally returns
a too small height for a given width."""
def create(self):
self._color = None
self.native = NSTextView.alloc().init()
self.native.impl = self
self.native.interface = self.interface
self.native.drawsBackground = False
self.native.editable = False
self.native.selectable = True
self.native.textContainer.lineFragmentPadding = 0
self.native.bezeled = False
# Add the layout constraints
self.add_constraints()
def set_html(self, value):
attr_str = attributed_str_from_html(value, self.native.font, color=self._color)
self.native.textStorage.setAttributedString(attr_str)
self.rehint()
def set_font(self, value):
if value:
self.native.font = value._impl.native
def set_color(self, value):
if value:
self._color = native_color(value)
# update html
self.set_html(self.interface.html)
def rehint(self):
self.interface.intrinsic.width = at_least(self.interface.MIN_WIDTH)
self.interface.intrinsic.height = at_least(self.interface.MIN_HEIGHT) The "magic" lies in the function def attributed_str_from_html(raw_html, font=None, color=None):
"""Converts html to a NSAttributed string using the system font family and color."""
html_value = """
<span style="font-family: '{0}'; font-size: {1}; color: {2}">
{3}
</span>
"""
font_family = font.fontName if font else 'system-ui'
font_size = font.pointSize if font else 13
color = color or NSColor.labelColor
c = color.colorUsingColorSpace(NSColorSpace.deviceRGBColorSpace)
c_str = f'rgb({c.redComponent * 255},{c.blueComponent * 255},{c.greenComponent * 255})'
html_value = html_value.format(font_family, font_size, c_str, raw_html)
nsstring = NSString(at(html_value))
data = nsstring.dataUsingEncoding(NSUTF8StringEncoding)
attr_str = NSMutableAttributedString.alloc().initWithHTML(
data,
documentAttributes=None,
)
return attr_str |
If interested, I can clean up the code and submit a PR. |
@samschott Definitely interested in the feature; and the general approach you've taken makes sense on Cocoa. I guess my question is whether this needs to be a different widget. |
I think this would indeed be a nicer API. However, the implementation will be a bit more difficult: I have not managed to make hrefs clickable in an NSTextField. Furthermore, if the text is "selectable" and the user selects its, all rich attributes get removed. I have therefore used a NSTextView instead. This is automatically multiline but maybe one can force it to be single line instead and get the appropriate width from the layout manager of the NSTextView. |
@freakboy3742 I am trying to put together a PR that introduces simple html rendering capabilities to a Label. IMO, there are two options:
Any preference? |
I think 2 properties makes sense as a safety mechanism; it won't always be obvious why The API doesn't need to be that complex, though - if "html" is the canonical representation, any call to I also wouldn't be opposed to introducing some light tag parsing - stripping out tags that we don't support (essentially, we'd only be preserving |
Fair point. Also, there is yet another option: have a single I'll look into stripping certain html tags. Python's XML parser may have trouble with self-closing html tags. |
I see what you're saying; however, I think I prefer the clarify of:
vs
If we were going to add support for other formats, I think I'd rather take an approach that lets end-users define the formatting - e.g., allow the value of |
My 2 cents: my_label.text = 'some plain text'
other_label.text = 'some <b>important</b> text'
other_label.text_format = 'html'
other_label.rendered_text -> would probably be always HTML no matter the input format? This would keep the API stable my_label.text = 'some plain text'
other_label.html = 'some <b>important</b> text' This may be more concise but adding new formats would imply having new properties that need to be created? |
This effectively favours html over other markup languages. I still prefer the more egalitarian approach of a Regarding the interface with the implementation layer, I do think that we should choose html as the markup language to pass all formatted text. AppKit's attributed strings can be generated from html but I am not so sure about Gtk labels with pango markup. Also, reliable conversion from html to RTF seems challenging. Maybe the solution is really to support a very limited subset of html tags and perform the conversion "manually". |
I agree that it favours HTML - however, the fact is: HTML is a favoured format, if only in the sense that it's a markup language that (a) is commonly understood, and (b) is often provided natively as an API for displaying rich text. The use case you're describing is " in, pretty display out" - and it would be highly unusual case where HTML wasn't a supported output for a markup format. The downside I see to the I'd also be completely OK with a very limited subset of HTML being supported (hence my earlier comment about parsing and stripping). Even if it was just |
What is toga's policy on dependencies? bleach seems to do exactly the type of white-listed html stripping which we need. |
[Commenting here as #1237 was closed] What was the outcome of all this conversation? Thanks! |
@massenz The outcome is "Yes, sure, we'd like this, but someone needs to implement it". In the meantime, no - there isn't an easy workaround. You might be able to use some of the sample code from this thread in your own app, however. |
First of all I know that toga is not aimed to support widget primitives.
But there is already
Label
widget that serves for quite low level purposes:So I think it may be useful as useful is html tag.
Label
-inherited POC for Winforms:My usecase: a dead simple GUI - with link to my repository. I wanted to avoid strange
Button
with URL inside (alternatively clickable github icon would be OK but not supported currently #774 outside of native commands palette), or createGroup
named "About" inside which will be "See on github" action, as it would be the only one command and on Windows it feels strange.The text was updated successfully, but these errors were encountered: