import Adw import Gtk public extension ComboRow { /// Creates a ComboRow with static text, items, and selection behavior. /// /// Values are applied once when the widget mounts. The callback receives the selected item string. /// - Parameter title: The row title. /// - Parameter subtitle: The optional row subtitle. /// - Parameter items: The optional strings shown by the row. /// - Parameter onSelectedItem: The optional selection callback. @_disfavoredOverload init(_ title: S, subtitle: String? = nil, items: [String]? = nil, _ onSelectedItem: ((String) -> Void)? = nil) { self.init() self = self.title(String(title)) if let subtitle { self = self.subtitle(subtitle) } if let items { self = self.comboItems(ComboItems(items), onSelectedItem: onSelectedItem) } } /// Creates a ComboRow whose text and items follow bindings. /// /// Binding modifiers update the widget in place and write widget changes back. Replacing the model resets selection to its first item. /// - Parameter title: The title binding. /// - Parameter subtitle: The optional subtitle binding. /// - Parameter items: The optional item-list binding. /// - Parameter onSelectedItem: The optional selection callback. init(_ title: Portico.Binding, subtitle: Portico.Binding? = nil, items: Portico.Binding<[String]>? = nil, _ onSelectedItem: ((String) -> Void)? = nil) { self.init() self = self.title(title) if let subtitle { self = self.subtitle(subtitle) } if let items { let holder = ComboItems(items.untrackedValue) self = self.comboItems(holder, onSelectedItem: onSelectedItem) self = self.appending { w, ctx in ctx.registry.add(items.subscribe { [w] values in holder.values = values w.setModel(model: Gtk.StringList(strings: values)) }) } } } /// Creates a ComboRow with live interpolated text values. /// /// Interpolated segments are re-evaluated when their dependencies change. The callback receives the selected item string. /// - Parameter title: The interpolated title. /// - Parameter subtitle: The optional interpolated subtitle. /// - Parameter items: The optional strings shown by the row. /// - Parameter onSelectedItem: The optional selection callback. init(_ title: Portico.InterpolatedText, subtitle: Portico.InterpolatedText? = nil, items: [String]? = nil, _ onSelectedItem: ((String) -> Void)? = nil) { self.init() self = self.title(title) if let subtitle { self = self.subtitle(subtitle) } if let items { self = self.comboItems(ComboItems(items), onSelectedItem: onSelectedItem) } } } /// Mutable item storage shared by a combo model and its selection callback. @MainActor private final class ComboItems { var values: [String] init(_ values: [String]) { self.values = values } } private extension ComboRow { /// Installs a string model and reports valid selection changes. /// /// Selection positions that are invalid or outside the current item list are ignored. func comboItems(_ items: ComboItems, onSelectedItem: ((String) -> Void)?) -> Self { var view = appending { w, _ in w.setModel(model: Gtk.StringList(strings: items.values)) } guard let onSelectedItem else { return view } view = view.appending { w, ctx in var last = w.getSelected() ctx.registry.add(w.connectNotify(detail: nil) { [w] _, _ in let position = w.getSelected() guard position != last else { return } last = position guard position != UInt32.max, Int(position) < items.values.count else { return } onSelectedItem(items.values[Int(position)]) }) } return view } }