Is it possible to use Pango markup or something else to give a GtkLabel a list of positions, which should be of highest priority when choosing where to wrap the text? I know that can already be achieved using GtkFlowBox and multiple GtkLabel’s (or similar) but using just one GtkLabel would result in much simpler code. I’ve been eyeing the segment="sentence"
attribute in particular, but I’m not sure how it works and if it does anything at all.
After looking through Pango’s and Gtk’s source code, I have determined that what I want is simply not there. So I made it myself:
import gi
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
from dataclasses import dataclass
from gi.repository import Gdk, Gtk
class SegmentedLabel(Gtk.Widget):
def __init__(self, segment_props={}, **kwargs):
super().__init__(layout_manager=self.Layout(), **kwargs)
self.segment_props = segment_props
def __del__(self):
while (i := self.get_first_child()) is not None:
i.unparent()
def set_segments(self, segments):
self.__del__()
for i in segments:
Gtk.Label(label=i, justify=Gtk.Justification.CENTER, **self.segment_props).set_parent(self)
@property
def children(self):
i = self.get_first_child()
while i is not None:
yield i
i = i.get_next_sibling()
class Layout(Gtk.LayoutManager):
def do_get_request_mode(self, _):
return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH
@dataclass
class Row:
width: int
height: int
segments: list[Gtk.Label]
def get_rows(self, label, width):
result = []
row = self.Row(0, 0, [])
for child in label.children:
nat = child.get_preferred_size().natural_size
if row.width + nat.width > width and row.segments:
result.append(row)
row = self.Row(0, 0, [])
if nat.width > width:
min, nat, _, _ = child.measure(Gtk.Orientation.VERTICAL, width)
assert min == nat
result.append(self.Row(width, min, [child]))
else:
row.width += nat.width
row.height = max(row.height, nat.height)
row.segments.append(child)
if row.segments:
result.append(row)
return result
def do_measure(self, label, orientation, width):
if orientation == Gtk.Orientation.HORIZONTAL:
assert width == -1
min, nat = 0, 0
for child in label.children:
child_min, child_nat, _, _ = child.measure(orientation, -1)
min = max(min, child_min)
nat += child_nat
return min, nat, -1, -1
elif width == -1:
min, nat = 0, 0
for child in label.children:
child_min, child_nat, _, _ = child.measure(orientation, -1)
min = max(min, child_min)
nat = max(nat, child_nat)
return min, nat, -1, -1
else:
height = sum(row.height for row in self.get_rows(label, width))
return height, height, -1, -1
def do_allocate(self, label, width, height, _):
rows = self.get_rows(label, width)
rect = Gdk.Rectangle()
rect.y = (height - sum(i.height for i in rows)) / 2
for row in rows:
rect.height = row.height
if len(row.segments) == 1:
rect.x = 0
rect.width = width
row.segments[0].size_allocate(rect, -1)
else:
rect.x = (width - row.width) / 2
for segment in row.segments:
rect.width = segment.get_preferred_size().natural_size.width
segment.size_allocate(rect, -1)
rect.x += rect.width
rect.y += rect.height
class Application(Gtk.Application):
def do_activate(self):
window = Gtk.ApplicationWindow(application=self)
# Need to keep a reference because Gtk.Window doesn't do that for us.
self.label = SegmentedLabel(
{
'wrap': True,
'use_markup': True,
},
margin_start=50,
margin_end=50,
margin_top=50,
margin_bottom=50,
css_classes=['frame'],
)
self.label.set_segments([
'Question: ',
'<i>What do you think of it?</i> ',
'Answer: ',
'<i>Lorem ipsum dolor sit amet.</i>',
])
window.set_child(self.label)
window.present()
Application(application_id='com.example.SegmentedLabel').run()
May this code be of use to anyone who encounters a similar problem.
This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.