Sat, 04 Oct 2025 14:52:59 +0200
fix repolist menu button
--- a/.hgignore Sun Aug 24 15:24:16 2025 +0200 +++ b/.hgignore Sat Oct 04 14:52:59 2025 +0200 @@ -1,6 +1,7 @@ relre:^build/.*$ relre:^config.mk$ relre:^core$ +relre:^gmon.out$ relre:^make/vs/.vs/.* relre:^make/vs/packages/.* relre:^make/vs/.*vcxproj\.user
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/BoxContainer.h Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,39 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "Container.h" +#import "GridLayout.h" + +@interface BoxContainer : GridLayout + +@property NSUserInterfaceLayoutOrientation orientation; + +- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing; + +@end +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/BoxContainer.m Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,33 @@ + + +#import "BoxContainer.h" + +@implementation BoxContainer + +- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing { + self = [super init]; + _orientation = orientation; + self.columnspacing = spacing; + self.rowspacing = spacing; + + return self; +} + +- (void) addView:(NSView*)view { + UiLayout layout = self.uilayout; + if(_orientation == NSUserInterfaceLayoutOrientationVertical) { + layout.hexpand = TRUE; + layout.hfill = TRUE; + } else { + layout.vexpand = TRUE; + layout.vfill = TRUE; + self.newline = FALSE; + } + self.uilayout = layout; + [super addView:view]; + if(_orientation == NSUserInterfaceLayoutOrientationVertical) { + self.newline = TRUE; + } +} + +@end
--- a/ui/cocoa/GridLayout.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/GridLayout.h Sat Oct 04 14:52:59 2025 +0200 @@ -61,8 +61,6 @@ @property CxList *children; @property NSSize preferredSize; -@property NSButton *test; - @property int x; @property int y; @property int cols;
--- a/ui/cocoa/GridLayout.m Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/GridLayout.m Sat Oct 04 14:52:59 2025 +0200 @@ -41,6 +41,8 @@ _columnspacing = 0; _rowspacing = 0; _children = cxArrayListCreateSimple(sizeof(GridElm), 32); + _preferredSize.width = -1; + _preferredSize.height = -1; return self; } @@ -80,12 +82,6 @@ NSSize size = elm->view.intrinsicContentSize; NSSize size2 = elm->view.fittingSize; - NSEdgeInsets alignment = elm->view.alignmentRectInsets; - // TODO: remove alignment - alignment.left = 0; - alignment.right = 0; - alignment.top = 0; - alignment.bottom = 0; if(size.width == NSViewNoIntrinsicMetric) { size.width = size2.width; } @@ -93,14 +89,14 @@ size.height = size2.height; } if(size.width != NSViewNoIntrinsicMetric) { - CGFloat width = size.width + alignment.left + alignment.right; + CGFloat width = size.width; if(width > cols[elm->x].preferred_size && elm->colspan <= 1 && span_max == 1) { cols[elm->x].preferred_size = width; } elm->preferred_width = width; } if(size.height != NSViewNoIntrinsicMetric) { - CGFloat height = size.height + alignment.top + alignment.right; + CGFloat height = size.height; //CGFloat height = size.height; if(height > rows[elm->y].preferred_size && elm->rowspan <= 1 && span_max == 1) { rows[elm->y].preferred_size = height; @@ -290,10 +286,16 @@ - (NSSize)intrinsicContentSize { + if(_preferredSize.width == -1) { + [self layout]; + } return self.preferredSize; } - (void) addView:(NSView*)view { + _preferredSize.width = -1; + _preferredSize.height = -1; + if(_newline) { _y++; _x = 0;
--- a/ui/cocoa/MainWindow.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/MainWindow.h Sat Oct 04 14:52:59 2025 +0200 @@ -31,7 +31,9 @@ @interface MainWindow : NSWindow -- (MainWindow*)init:(UiObject*)obj; +@property (strong) NSView *sidebar; + +- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar; @end
--- a/ui/cocoa/MainWindow.m Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/MainWindow.m Sat Oct 04 14:52:59 2025 +0200 @@ -29,6 +29,7 @@ #import "MainWindow.h" #import "Container.h" #import "GridLayout.h" +#import "BoxContainer.h" #import "../common/object.h" #import <objc/runtime.h> @@ -38,7 +39,7 @@ @implementation MainWindow -- (MainWindow*)init:(UiObject*)obj { +- (MainWindow*)init:(UiObject*)obj withSidebar:(BOOL)sidebar { NSRect frame = NSMakeRect(300, 200, 600, 500); self = [self initWithContentRect:frame @@ -49,24 +50,52 @@ backing:NSBackingStoreBuffered defer:false]; + if(uic_toolbar_isenabled()) { UiToolbar *toolbar = [[UiToolbar alloc]initWithObject:obj]; [self setToolbar:toolbar]; } + int top = 4; + NSView *content = self.contentView; + if(sidebar) { + self.styleMask |= NSWindowStyleMaskFullSizeContentView; + self.titleVisibility = NSWindowTitleHidden; + self.titlebarAppearsTransparent = YES; + + NSSplitView *splitview = [[NSSplitView alloc]init]; + splitview.vertical = YES; + splitview.dividerStyle = NSSplitViewDividerStyleThin; + splitview.translatesAutoresizingMaskIntoConstraints = false; + [self.contentView addSubview:splitview]; + + [NSLayoutConstraint activateConstraints:@[ + [splitview.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:0], + [splitview.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], + [splitview.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], + [splitview.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] + ]]; + + _sidebar = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:_sidebar]; + + content = [[NSView alloc]initWithFrame:NSMakeRect(0,0,100,100)]; + [splitview addArrangedSubview:content]; + + top = 34; + } // create a vertical stackview as default container BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:0]; //GridLayout *vbox = [[GridLayout alloc] init]; vbox.translatesAutoresizingMaskIntoConstraints = false; - [self.contentView addSubview:vbox]; + [content addSubview:vbox]; [NSLayoutConstraint activateConstraints:@[ - [vbox.topAnchor constraintEqualToAnchor:self.contentView.topAnchor constant:4], - [vbox.leadingAnchor constraintEqualToAnchor:self.contentView.leadingAnchor], - [vbox.trailingAnchor constraintEqualToAnchor:self.contentView.trailingAnchor], - [vbox.bottomAnchor constraintEqualToAnchor:self.contentView.bottomAnchor] + [vbox.topAnchor constraintEqualToAnchor:content.topAnchor constant:top], + [vbox.leadingAnchor constraintEqualToAnchor:content.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:content.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:content.bottomAnchor], ]]; - uic_object_push_container(obj, ui_create_container(obj, vbox)); return self; @@ -75,7 +104,6 @@ @end - @implementation MainWindowController - (MainWindowController*)initWithWindow:(UiObject*)obj window:(NSWindow*)window { @@ -273,3 +301,27 @@ index++; } } + + +UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args) { + MainWindow *window = (__bridge MainWindow*)obj->wobj; + if(window.sidebar == nil) { + return NULL; + } + NSView *sidebar = window.sidebar; + + // create a vertical stackview as default container + BoxContainer *vbox = [[BoxContainer alloc] init:NSUserInterfaceLayoutOrientationVertical spacing:args->spacing]; + //GridLayout *vbox = [[GridLayout alloc] init]; + vbox.translatesAutoresizingMaskIntoConstraints = false; + [sidebar addSubview:vbox]; + [NSLayoutConstraint activateConstraints:@[ + [vbox.topAnchor constraintEqualToAnchor:sidebar.topAnchor constant:34], + [vbox.leadingAnchor constraintEqualToAnchor:sidebar.leadingAnchor], + [vbox.trailingAnchor constraintEqualToAnchor:sidebar.trailingAnchor], + [vbox.bottomAnchor constraintEqualToAnchor:sidebar.bottomAnchor] + ]]; + uic_object_push_container(obj, ui_create_container(obj, vbox)); + + return NULL; +}
--- a/ui/cocoa/Toolbar.m Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/Toolbar.m Sat Oct 04 14:52:59 2025 +0200 @@ -204,6 +204,7 @@ } i->obj = (__bridge void*)seg; + printf("seg: %p\n", seg); i->get = ui_toolbar_seg_toggleitem_get; i->set = ui_toolbar_seg_toggleitem_set; }
--- a/ui/cocoa/container.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/container.h Sat Oct 04 14:52:59 2025 +0200 @@ -75,14 +75,6 @@ @end -@interface BoxContainer : NSStackView<Container> - -- (BoxContainer*)init:(NSUserInterfaceLayoutOrientation)orientation spacing:(int)spacing; - -@end - - - UiContainerX* ui_create_container(UiObject *obj, id<Container> container);
--- a/ui/cocoa/container.m Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/container.m Sat Oct 04 14:52:59 2025 +0200 @@ -28,9 +28,11 @@ #import "Container.h" #import "GridLayout.h" +#import "BoxContainer.h" /* ------------------------- container classes ------------------------- */ +/* @implementation BoxContainer @synthesize label=_label; @@ -88,7 +90,7 @@ } @end - +*/ /* -------------------- public container functions --------------------- */ @@ -129,7 +131,6 @@ return (__bridge void*)grid; } - void ui_container_begin_close(UiObject *obj) { UiContainerX *ct = obj->container_end; ct->close = 1;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/entry.h Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,61 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "toolkit.h" +#import "Container.h" + +#import "../ui/entry.h" + +@interface UiSpinBox : NSObject<NSTextFieldDelegate> + +@property UiObject *obj; +@property (weak) NSTextField *textfield; +@property (weak) NSStepper *stepper; +@property BOOL isInteger; +@property ui_callback onchange; +@property void* onchangedata; +@property UiObserver **observers; + +- (UiSpinBox*)init; + +- (void)valueChanged; +- (void)stepperChanged:(id)sender; +- (void) controlTextDidChange:(NSNotification *) obj; + +@end + +int64_t ui_spinbutton_getint(UiInteger *i); +void ui_spinbutton_setint(UiInteger *i, int64_t val); + +double ui_spinbutton_getdouble(UiDouble *d); +void ui_spinbutton_setdouble(UiDouble *d, double val); + +double ui_spinbutton_getrangeval(UiRange *r); +void ui_spinbutton_setrangeval(UiRange *r, double val); +void ui_spinbutton_setrange(UiRange *r, double min, double max); +void ui_spinbutton_setextent(UiRange *r, double extent);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/entry.m Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,272 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "entry.h" +#import <objc/runtime.h> + +@implementation UiSpinBox + +- (UiSpinBox*)init { + return self; +} + +- (void)valueChanged { + float value = _stepper.doubleValue; + UiEvent e; + e.obj = _obj; + e.window = e.obj->window; + e.document = e.obj->ctx->document; + e.eventdata = NULL; + e.eventdatatype = 0; + e.intval = (int)value; + e.set = ui_get_setop(); + + if(_onchange) { + _onchange(&e, _onchangedata); + } + + if(_observers) { + UiObserver *observer = *_observers; + ui_notify_evt(observer, &e); + } +} + +- (void)stepperChanged:(id)sender { + if(_isInteger) { + _textfield.integerValue = _stepper.integerValue; + } else { + _textfield.doubleValue = _stepper.doubleValue; + } + [self valueChanged]; +} + +- (void) controlTextDidChange:(NSNotification *)obj { + if(_isInteger) { + _stepper.integerValue = _textfield.integerValue; + } else { + _stepper.doubleValue = _textfield.doubleValue; + } + [self valueChanged]; +} + +@end + +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + double min = args->min; + double max = args->max != 0 ? args->max : 1000; + + UiVar *var = NULL; + UiVarType vartype = 0; + if(args->varname) { + var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(!var) { + if(args->intvalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; + } else if(args->doublevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; + } else if(args->rangevalue) { + var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } + } + + if(vartype == UI_VAR_RANGE) { + UiRange *r = var->value; + min = r->min; + max = r->max; + } + if(args->step == 0) { + args->step = 1; + } + + // create and setup textfield for number input + NSTextField *textfield = [[NSTextField alloc] init]; + textfield.translatesAutoresizingMaskIntoConstraints = NO; + + if(!args->hfill || args->width > 0) { + textfield.translatesAutoresizingMaskIntoConstraints = NO; + int width = args->width > 0 ? args->width : 100; + [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES]; + } + + NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; + formatter.numberStyle = NSNumberFormatterDecimalStyle; + formatter.allowsFloats = vartype != UI_VAR_INTEGER; + formatter.minimumFractionDigits = args->digits; + formatter.maximumFractionDigits = args->digits; + + textfield.formatter = formatter; + + // create view containing the textfield and stepper + NSView *view = [[NSView alloc]init]; + view.translatesAutoresizingMaskIntoConstraints = NO; + + NSStepper *stepper = [[NSStepper alloc] init]; + stepper.translatesAutoresizingMaskIntoConstraints = NO; + + [view addSubview:textfield]; + [view addSubview:stepper]; + + [NSLayoutConstraint activateConstraints:@[ + [textfield.leadingAnchor constraintEqualToAnchor:view.leadingAnchor], + [textfield.topAnchor constraintEqualToAnchor:view.topAnchor], + [textfield.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [stepper.trailingAnchor constraintEqualToAnchor:view.trailingAnchor], + [stepper.topAnchor constraintEqualToAnchor:view.topAnchor], + [stepper.bottomAnchor constraintEqualToAnchor:view.bottomAnchor], + + [textfield.trailingAnchor constraintEqualToAnchor:stepper.leadingAnchor] + ]]; + + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, view, &layout); + + // create the spinbox object, that handles all textfield and stepper events + UiSpinBox *spinbox = [[UiSpinBox alloc]init]; + spinbox.obj = obj; + spinbox.textfield = textfield; + spinbox.stepper = stepper; + spinbox.onchange = args->onchange; + spinbox.onchangedata = args->onchangedata; + spinbox.isInteger = vartype == UI_VAR_INTEGER; + objc_setAssociatedObject(stepper, "ui_spinbox", spinbox, OBJC_ASSOCIATION_RETAIN); + + stepper.minValue = min; + stepper.maxValue = max; + stepper.increment = args->step; + stepper.target = spinbox; + stepper.action = @selector(stepperChanged:); + textfield.delegate = spinbox; + + UiObserver **obs = NULL; + if(var) { + void *varObj = (__bridge void*)spinbox; + switch(vartype) { + default: break; + case UI_VAR_INTEGER: { + UiInteger *i = var->value; + i->get = ui_spinbutton_getint; + i->set = ui_spinbutton_setint; + i->obj = varObj; + obs = &i->observers; + + stepper.integerValue = i->value; + textfield.integerValue = i->value; + break; + } + case UI_VAR_DOUBLE: { + UiDouble *d = var->value; + d->get = ui_spinbutton_getdouble; + d->set = ui_spinbutton_setdouble; + d->obj = varObj; + obs = &d->observers; + + stepper.doubleValue = d->value; + textfield.doubleValue = d->value; + break; + } + case UI_VAR_RANGE: { + UiRange *r = var->value; + r->get = ui_spinbutton_getrangeval; + r->set = ui_spinbutton_setrangeval; + r->setrange = ui_spinbutton_setrange; + r->setextent = ui_spinbutton_setextent; + r->obj = varObj; + obs = &r->observers; + + stepper.doubleValue = r->value; + textfield.doubleValue = r->value; + break; + } + } + } + spinbox.observers = obs; + + return (__bridge void*)textfield; +} + +int64_t ui_spinbutton_getint(UiInteger *i) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; + i->value = spinbox.stepper.integerValue; + return i->value; +} + +void ui_spinbutton_setint(UiInteger *i, int64_t val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)i->obj; + i->value = val; + spinbox.stepper.integerValue = val; + spinbox.textfield.integerValue = val; +} + +double ui_spinbutton_getdouble(UiDouble *d) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; + d->value = spinbox.stepper.doubleValue; + return d->value; +} + +void ui_spinbutton_setdouble(UiDouble *d, double val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)d->obj; + d->value = val; + spinbox.stepper.doubleValue = val; + spinbox.textfield.doubleValue = val; +} + +double ui_spinbutton_getrangeval(UiRange *r) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + r->value = spinbox.stepper.doubleValue; + return r->value; +} + +void ui_spinbutton_setrangeval(UiRange *r, double val) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + r->value = val; + spinbox.stepper.doubleValue = val; + spinbox.textfield.doubleValue = val; +} + +void ui_spinbutton_setrange(UiRange *r, double min, double max) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + spinbox.stepper.minValue = min; + spinbox.stepper.maxValue = max; +} + +void ui_spinbutton_setextent(UiRange *r, double extent) { + UiSpinBox *spinbox = (__bridge UiSpinBox*)r->obj; + spinbox.stepper.increment = extent; +}
--- a/ui/cocoa/objs.mk Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/objs.mk Sat Oct 04 14:52:59 2025 +0200 @@ -32,8 +32,9 @@ COCOAOBJ = toolkit.o COCOAOBJ += AppDelegate.o COCOAOBJ += GridLayout.o +COCOAOBJ += BoxContainer.o COCOAOBJ += EventData.o -COCOAOBJ += UiJob.o +COCOAOBJ += UiThread.o COCOAOBJ += MainWindow.o COCOAOBJ += WindowManager.o COCOAOBJ += window.o @@ -42,6 +43,13 @@ COCOAOBJ += text.o COCOAOBJ += menu.o COCOAOBJ += Toolbar.o +COCOAOBJ += ListDelegate.o +COCOAOBJ += ListDataSource.o +COCOAOBJ += label.o +COCOAOBJ += list.o +COCOAOBJ += widget.o +COCOAOBJ += image.o +COCOAOBJ += entry.o TOOLKITOBJS += $(COCOAOBJ:%=$(COCOA_OBJPRE)%) TOOLKITSOURCE += $(COCOAOBJ:%.o=cocoa/%.m)
--- a/ui/cocoa/text.m Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/text.m Sat Oct 04 14:52:59 2025 +0200 @@ -182,7 +182,14 @@ textfield = [[NSSecureTextField alloc] init]; } else { textfield = [[NSTextField alloc] init]; - } + } + + if(!args->hfill || args->width > 0) { + textfield.translatesAutoresizingMaskIntoConstraints = NO; + int width = args->width > 0 ? args->width : 100; + [[textfield.widthAnchor constraintEqualToConstant:width] setActive:YES]; + } + if(frameless) { [textfield setBezeled: NO];
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/widget.h Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,31 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "toolkit.h" +#import "container.h" +#import "../ui/widget.h"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/cocoa/widget.m Sat Oct 04 14:52:59 2025 +0200 @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#import "widget.h" + +/* genereal widget functions */ + +void ui_set_enabled(UIWIDGET widget, int enabled) { + NSControl *control = (__bridge NSControl*)widget; + control.enabled = enabled; +} + +void ui_set_show_all(UIWIDGET widget, int value) { + // TODO: is this relevant? +} + +void ui_set_visible(UIWIDGET widget, int visible) { + NSView *view = (__bridge NSView*)widget; + view.hidden = !visible; +} + + + +/* custom widget */ + +UIWIDGET ui_customwidget_create(UiObject *obj, ui_createwidget_func create_widget, void *userdata, UiWidgetArgs *args) { + UIWIDGET widget = create_widget(obj, args, userdata); + + NSView *view = (__bridge NSView*)widget; + UiLayout layout = UI_INIT_LAYOUT(args); + ui_container_add(obj, view, &layout); + + return widget; +}
--- a/ui/cocoa/window.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/window.h Sat Oct 04 14:52:59 2025 +0200 @@ -26,4 +26,5 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#import "toolkit.h" \ No newline at end of file +#import "toolkit.h" +#import "../ui/window.h"
--- a/ui/cocoa/window.m Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/cocoa/window.m Sat Oct 04 14:52:59 2025 +0200 @@ -42,14 +42,14 @@ #include <cx/mempool.h> -static UiObject* create_window(const char *title, BOOL simple) { +static UiObject* create_window(const char *title, BOOL simple, BOOL sidebar) { CxMempool *mp = cxMempoolCreateSimple(256); UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ref = 0; obj->ctx = uic_context(obj, mp); - MainWindow *window = [[MainWindow alloc] init:obj]; + MainWindow *window = [[MainWindow alloc] init:obj withSidebar:sidebar]; [[WindowManager sharedWindowManager] addWindow:window]; window.releasedWhenClosed = false; @@ -64,13 +64,169 @@ } UiObject* ui_window(const char *title, void *window_data) { - UiObject *obj = create_window(title, FALSE); + UiObject *obj = create_window(title, FALSE, FALSE); obj->window = window_data; return obj; } UiObject* ui_simple_window(const char *title, void *window_data) { - UiObject *obj = create_window(title, TRUE); + UiObject *obj = create_window(title, TRUE, FALSE); + obj->window = window_data; + return obj; +} + +UiObject* ui_sidebar_window(const char *title, void *window_data) { + UiObject *obj = create_window(title, FALSE, TRUE); obj->window = window_data; return obj; } + +/* --------------------------------- File Dialogs --------------------------------- */ + +void ui_openfiledialog(UiObject *obj, unsigned int mode, ui_callback file_selected_callback, void *cbdata) { + NSOpenPanel *openPanel = [NSOpenPanel openPanel]; + if((mode & UI_FILEDIALOG_SELECT_MULTI) == UI_FILEDIALOG_SELECT_MULTI) { + openPanel.allowsMultipleSelection = YES; + } + if((mode & UI_FILEDIALOG_SELECT_FOLDER) == UI_FILEDIALOG_SELECT_FOLDER) { + openPanel.canChooseFiles = NO; + openPanel.canChooseDirectories = YES; + } + + NSWindow *window = (__bridge NSWindow*)obj->wobj; + [openPanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.intval = 0; + event.set = 0; + + UiFileList flist = { NULL, 0 }; + event.eventdata = &flist; + event.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + if(result == NSModalResponseOK) { + NSArray<NSURL *> *urls = [openPanel URLs]; + flist.files = calloc(urls.count, sizeof(char*)); + for(NSURL *url in urls) { + if([url isFileURL]) { + flist.files[flist.nfiles++] = strdup(url.path.UTF8String); + } + } + } + + if(file_selected_callback) { + file_selected_callback(&event, cbdata); + } + ui_filelist_free(flist); + }]; +} + +void ui_savefiledialog(UiObject *obj, const char *name, ui_callback file_selected_callback, void *cbdata) { + NSSavePanel *savePanel = [NSSavePanel savePanel]; + if(name) { + NSString *nameStr = [[NSString alloc] initWithUTF8String:name]; + [savePanel setNameFieldStringValue: nameStr]; + } + + NSWindow *window = (__bridge NSWindow*)obj->wobj; + [savePanel beginSheetModalForWindow:window completionHandler:^(NSModalResponse result) { + UiEvent event; + event.obj = obj; + event.window = obj->window; + event.document = obj->ctx->document; + event.intval = 0; + event.set = 0; + + UiFileList flist = { NULL, 0 }; + event.eventdata = &flist; + event.eventdatatype = UI_EVENT_DATA_FILE_LIST; + + if(result == NSModalResponseOK) { + NSURL *url = [savePanel URL]; + if([url isFileURL]) { + NSString *path = url.path; + flist.files = malloc(sizeof(char*)); + flist.files[0] = strdup(path.UTF8String); + flist.nfiles = 1; + } + file_selected_callback(NULL, NULL); + } + if(file_selected_callback) { + file_selected_callback(&event, cbdata); + } + ui_filelist_free(flist); + }]; +} + +/* ------------------------------------- Dialog ------------------------------------- */ + +void ui_dialog_create(UiObject *parent, UiDialogArgs *args) { + NSAlert *dialog = [[NSAlert alloc] init]; + + if(args->title) { + dialog.messageText = [[NSString alloc]initWithUTF8String:args->title]; + } + if(args->content) { + dialog.informativeText = [[NSString alloc]initWithUTF8String:args->content]; + } + NSTextField *textfield = nil; + if(args->input) { + NSRect frame = NSMakeRect(0,0,300,22); + textfield = args->password ? [[NSSecureTextField alloc] initWithFrame:frame] : [[NSTextField alloc]initWithFrame:frame]; + if(args->input_value) { + textfield.stringValue = [[NSString alloc]initWithUTF8String:args->input_value]; + } + dialog.accessoryView = textfield; + } + + int b = 0; + int b1 = -1; + int b2 = -1; + if(args->button1_label) { + [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->button1_label]]; + b1 = b++; + } + if(args->button2_label) { + [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->button2_label]]; + b2 = b; + } + if(args->closebutton_label) { + [dialog addButtonWithTitle:[[NSString alloc]initWithUTF8String:args->closebutton_label]]; + } + + ui_callback callback = args->result; + void *userdata = args->resultdata; + + NSWindow *window = (__bridge NSWindow*)parent->wobj; + [dialog beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) { + UiEvent event; + event.obj = parent; + event.window = event.obj->window; + event.document = event.obj->ctx->document; + event.eventdata = NULL; + event.eventdatatype = 0; + event.set = 0; + event.intval = 0; + + long ret = returnCode - NSAlertFirstButtonReturn; + if(ret == b1) { + event.intval = 1; + } else if(ret == b2) { + event.intval = 2; + } + + NSString *value = nil; + if(textfield) { + value = textfield.stringValue; + event.eventdata = (void*)value.UTF8String; + event.eventdatatype = UI_EVENT_DATA_STRING; + } + + if(callback) { + callback(&event, userdata); + } + }]; + +}
--- a/ui/common/args.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/args.c Sat Oct 04 14:52:59 2025 +0200 @@ -320,13 +320,16 @@ args->onclickdata = onclickdata; } -void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *groups) { - // TODO +void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_toolbar_item_args_free(UiToolbarItemArgs *args) { free((void*)args->label); free((void*)args->stockid); free((void*)args->icon); + free((void*)args->groups); free(args); } @@ -369,8 +372,10 @@ } -void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *groups) { - // TODO +void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args,int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } @@ -379,6 +384,7 @@ free((void*)args->stockid); free((void*)args->icon); free((void*)args->varname); + free((void*)args->groups); free(args); } @@ -424,7 +430,7 @@ } void ui_container_args_set_fill(UiContainerArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } void ui_container_args_set_hexpand(UiContainerArgs *args, UiBool value) { @@ -529,7 +535,7 @@ void ui_frame_args_set_fill(UiFrameArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -662,7 +668,7 @@ void ui_splitpane_args_set_fill(UiSplitPaneArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -735,6 +741,9 @@ args->initial_position = pos; } +void ui_splitpane_args_set_position_property(UiSplitPaneArgs *args, const char *propname) { + args->position_property = strdup(propname); +} void ui_splitpane_args_set_varname(UiSplitPaneArgs *args, const char *varname) { args->varname = strdup(varname); @@ -754,6 +763,115 @@ free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); + free((void*)args->position_property); + free(args); +} + + +/* ---------------------------- UiTabViewArgs ---------------------------- */ + +UiTabViewArgs* ui_tabview_args_new(void) { + UiTabViewArgs *args = malloc(sizeof(UiTabViewArgs)); + memset(args, 0, sizeof(UiTabViewArgs)); + return args; +} + +void ui_tabview_args_set_fill(UiTabViewArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_tabview_args_set_hexpand(UiTabViewArgs *args, UiBool value) { + args->hexpand = value; +} + + +void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value) { + args->vexpand = value; +} + + +void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value) { + args->hfill = value; +} + + +void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value) { + args->vfill = value; +} + + +void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value) { + args->override_defaults = value; +} + + +void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan) { + args->colspan = colspan; +} + + +void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan) { + args->rowspan = rowspan; +} + + +void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name) { + args->name = strdup(name); +} + + +void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + + +void ui_tabview_args_set_margin(UiTabViewArgs *args, int value) { + args->margin = value; +} + + +void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value) { + args->spacing = value; +} + + +void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value) { + args->columnspacing = value; +} + + +void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value) { + args->rowspacing = value; +} + +void ui_tabview_args_set_type(UiTabViewArgs *args, UiTabViewType tabview) { + args->tabview = tabview; +} + +void ui_tabview_args_set_onchange(UiTabViewArgs *args, ui_callback cb) { + args->onchange = cb; +} + +void ui_tabview_args_set_onchangedata(UiTabViewArgs *args, void *userdata) { + args->onchangedata = userdata; +} + +void ui_tabview_args_set_varname(UiTabViewArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_tabview_args_set_value(UiTabViewArgs *args, UiInteger *value) { + args->value = value; +} + +void ui_tabview_args_set_subcontainer(UiTabViewArgs *args, UiSubContainerType subcontainer) { + args->subcontainer = subcontainer; +} + +void ui_tabview_args_free(UiTabViewArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); free(args); } @@ -768,7 +886,7 @@ void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -834,7 +952,7 @@ void ui_label_args_set_fill(UiLabelArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -923,7 +1041,7 @@ void ui_progressbar_args_set_fill(UiProgressbarArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1004,7 +1122,7 @@ } void ui_progress_spinner_args_set_fill(UiProgressbarSpinnerArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } void ui_progress_spinner_args_set_hexpand(UiProgressbarSpinnerArgs *args, UiBool value) { @@ -1069,7 +1187,7 @@ void ui_button_args_set_fill(UiButtonArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1145,8 +1263,10 @@ args->onclickdata = onclickdata; } -void ui_button_args_set_groups(UiButtonArgs *args, int *groups){ - // TODO +void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_button_args_free(UiButtonArgs *args) { @@ -1171,7 +1291,7 @@ void ui_toggle_args_set_fill(UiToggleArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1259,8 +1379,10 @@ args->enable_group = group; } -void ui_toggle_args_set_groups(UiToggleArgs *args, int *groups){ - // TODO +void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_toggle_args_free(UiToggleArgs *args) { @@ -1285,7 +1407,7 @@ void ui_linkbutton_args_set_fill(UiLinkButtonArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1365,8 +1487,10 @@ args->value = value; } -void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups){ - // TODO +void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_linkbutton_args_free(UiLinkButtonArgs *args) { @@ -1389,7 +1513,7 @@ } void ui_list_args_set_fill(UiListArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } void ui_list_args_set_hexpand(UiListArgs *args, UiBool value) { @@ -1461,6 +1585,14 @@ args->getvalue2data = userdata; } +void ui_list_args_set_getstyle_func(UiListArgs *args, ui_getstylefunc getstyle) { + args->getstyle = getstyle; +} + +void ui_list_args_set_getstyle_data(UiListArgs *args, void *userdata) { + args->getstyledata = userdata; +} + void ui_list_args_set_onactivate(UiListArgs *args, ui_callback callback) { args->onactivate = callback; } @@ -1501,6 +1633,14 @@ args->ondropdata = userdata; } +void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave) { + args->onsave = onsave; +} + +void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata) { + args->onsavedata = userdata; +} + void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection) { args->multiselection = multiselection; } @@ -1509,8 +1649,10 @@ args->contextmenu = menubuilder; } -void ui_list_args_set_groups(UiListArgs *args, int *groups) { - // TODO +void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_list_args_free(UiListArgs *args) { @@ -1523,6 +1665,7 @@ } free(args->static_elements); } + free((void*)args->groups); free(args); } @@ -1538,7 +1681,7 @@ void ui_sourcelist_args_set_fill(UiSourceListArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1630,12 +1773,16 @@ args->onbuttonclickdata = userdata; } +void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder) { + args->contextmenu = menubuilder; +} void ui_sourcelist_args_free(UiSourceListArgs *args) { free((void*)args->name); free((void*)args->style_class); free((void*)args->varname); free((void*)args->sublists); + free((void*)args->groups); free(args); } @@ -1650,7 +1797,7 @@ void ui_textarea_args_set_fill(UiTextAreaArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1715,8 +1862,10 @@ args->value = value; } -void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *groups){ - // TODO +void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_textarea_args_free(UiTextAreaArgs *args) { @@ -1739,7 +1888,7 @@ void ui_textfield_args_set_fill(UiTextFieldArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1813,8 +1962,10 @@ args->value = value; } -void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *groups){ - // TODO +void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_textfield_args_free(UiTextFieldArgs *args) { @@ -1826,6 +1977,109 @@ } +/* ------------------------- UiSpinBoxArgs ----------------------------*/ + +UiSpinBoxArgs* ui_spinbox_args_new(void) { + UiSpinBoxArgs *args = malloc(sizeof(UiSpinBoxArgs)); + memset(args, 0, sizeof(UiSpinBoxArgs)); + return args; +} + +void ui_spinbox_args_set_fill(UiSpinBoxArgs *args, UiBool fill) { + args->fill = fill; +} + +void ui_spinbox_args_set_hexpand(UiSpinBoxArgs *args, UiBool value) { + args->hexpand = value; +} + +void ui_spinbox_args_set_vexpand(UiSpinBoxArgs *args, UiBool value) { + args->vexpand = value; +} + +void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value) { + args->hfill = value; +} + +void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value) { + args->vfill = value; +} + +void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value) { + args->override_defaults = value; +} + +void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan) { + args->colspan = colspan; +} + +void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan) { + args->rowspan = rowspan; +} + +void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name) { + args->name = strdup(name); +} + +void ui_spinbox_args_set_style_class(UiSpinBoxArgs *args, const char *classname) { + args->style_class = strdup(classname); +} + +void ui_spinbox_args_set_onchange(UiSpinBoxArgs *args, ui_callback callback) { + args->onchange = callback; +} + +void ui_spinbox_args_set_onchangedata(UiSpinBoxArgs *args, void *onchangedata) { + args->onchangedata = onchangedata; +} + +void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min) { + args->min = min; +} + +void ui_spinbox_args_set_max(UiSpinBoxArgs *args, double max) { + args->max = max; +} + +void ui_spinbox_args_set_step(UiSpinBoxArgs *args, double step) { + args->step = step; +} + +void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits) { + args->digits; +} + +void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname) { + args->varname = strdup(varname); +} + +void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value) { + args->intvalue = value; +} + +void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value) { + args->doublevalue = value; +} + +void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value) { + args->rangevalue = value; +} + +void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; +} + +void ui_spinbox_args_free(UiSpinBoxArgs *args) { + free((void*)args->name); + free((void*)args->style_class); + free((void*)args->varname); + free((void*)args->groups); + free(args); +} + + /* ------------------------- UiWebviewArgs ----------------------------*/ UiWebviewArgs* ui_webview_args_new(void) { @@ -1836,7 +2090,7 @@ void ui_webview_args_set_fill(UiWebviewArgs *args, UiBool fill) { - args->fill = fill ? UI_ON : UI_OFF; + args->fill = fill; } @@ -1892,8 +2146,10 @@ args->value = value; } -void ui_webview_args_set_groups(UiWebviewArgs *args, int *groups){ - // TODO +void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates) { + args->groups = calloc(numstates+1, sizeof(int)); + memcpy((void*)args->groups, states, numstates * sizeof(int)); + ((int*)args->groups)[numstates] = -1; } void ui_webview_args_free(UiWebviewArgs *args) {
--- a/ui/common/args.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/args.h Sat Oct 04 14:52:59 2025 +0200 @@ -33,6 +33,7 @@ #include "../ui/container.h" #include "../ui/display.h" #include "../ui/button.h" +#include "../ui/entry.h" #include "../ui/menu.h" #include "../ui/toolbar.h" #include "../ui/tree.h" @@ -108,7 +109,7 @@ UIEXPORT void ui_toolbar_item_args_set_icon(UiToolbarItemArgs *args, const char *icon); UIEXPORT void ui_toolbar_item_args_set_onclick(UiToolbarItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_item_args_set_onclickdata(UiToolbarItemArgs *args, void *onclickdata); -UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *groups); +UIEXPORT void ui_toolbar_item_args_set_groups(UiToolbarItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_item_args_free(UiToolbarItemArgs *args); UIEXPORT UiToolbarToggleItemArgs* ui_toolbar_toggleitem_args_new(void); @@ -118,7 +119,7 @@ UIEXPORT void ui_toolbar_toggleitem_args_set_varname(UiToolbarToggleItemArgs *args, const char *varname); UIEXPORT void ui_toolbar_toggleitem_args_set_onchange(UiToolbarToggleItemArgs *args, ui_callback callback); UIEXPORT void ui_toolbar_toggleitem_args_set_onchangedata(UiToolbarToggleItemArgs *args, void *onchangedata); -UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *groups); +UIEXPORT void ui_toolbar_toggleitem_args_set_groups(UiToolbarToggleItemArgs *args, int *states, int numstates); UIEXPORT void ui_toolbar_toggleitem_args_free(UiToolbarToggleItemArgs *args); UIEXPORT UiToolbarMenuArgs* ui_toolbar_menu_args_new(void); @@ -148,7 +149,6 @@ UIEXPORT void ui_container_args_set_rowspacing(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_free(UiContainerArgs *args); - UIEXPORT UiFrameArgs* ui_frame_args_new(void); UIEXPORT void ui_frame_args_set_fill(UiFrameArgs *args, UiBool fill); UIEXPORT void ui_frame_args_set_hexpand(UiFrameArgs *args, UiBool value); @@ -191,11 +191,35 @@ UIEXPORT void ui_splitpane_args_set_columnspacing(UiSplitPaneArgs *args, int value); UIEXPORT void ui_splitpane_args_set_rowspacing(UiSplitPaneArgs *args, int value); UIEXPORT void ui_splitpane_args_set_initial_position(UiSplitPaneArgs *args, int pos); +UIEXPORT void ui_splitpane_args_set_position_property(UiSplitPaneArgs *args, const char *propname); UIEXPORT void ui_splitpane_args_set_varname(UiSplitPaneArgs *args, const char *varname); UIEXPORT void ui_splitpane_args_set_value(UiSplitPaneArgs *args, UiInteger *value); UIEXPORT void ui_splitpane_args_set_max_panes(UiSplitPaneArgs *args, int max); UIEXPORT void ui_splitpane_args_free(UiSplitPaneArgs *args); +UIEXPORT UiTabViewArgs* ui_tabview_args_new(void); +UIEXPORT void ui_tabview_args_set_fill(UiTabViewArgs *args, UiBool fill); +UIEXPORT void ui_tabview_args_set_hexpand(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_vexpand(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_hfill(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_vfill(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_override_defaults(UiTabViewArgs *args, UiBool value); +UIEXPORT void ui_tabview_args_set_colspan(UiTabViewArgs *args, int colspan); +UIEXPORT void ui_tabview_args_set_rowspan(UiTabViewArgs *args, int rowspan); +UIEXPORT void ui_tabview_args_set_name(UiTabViewArgs *args, const char *name); +UIEXPORT void ui_tabview_args_set_style_class(UiTabViewArgs *args, const char *classname); +UIEXPORT void ui_tabview_args_set_margin(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_spacing(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_columnspacing(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_rowspacing(UiTabViewArgs *args, int value); +UIEXPORT void ui_tabview_args_set_type(UiTabViewArgs *args, UiTabViewType tabview); +UIEXPORT void ui_tabview_args_set_onchange(UiTabViewArgs *args, ui_callback cb); +UIEXPORT void ui_tabview_args_set_onchangedata(UiTabViewArgs *args, void *userdata); +UIEXPORT void ui_tabview_args_set_varname(UiTabViewArgs *args, const char *varname); +UIEXPORT void ui_tabview_args_set_value(UiTabViewArgs *args, UiInteger *value); +UIEXPORT void ui_tabview_args_set_subcontainer(UiTabViewArgs *args, UiSubContainerType subcontainer); +UIEXPORT void ui_tabview_args_free(UiTabViewArgs *args); + UIEXPORT UiWidgetArgs* ui_widget_args_new(void); UIEXPORT void ui_widget_args_set_fill(UiWidgetArgs *args, UiBool fill); UIEXPORT void ui_widget_args_set_hexpand(UiWidgetArgs *args, UiBool value); @@ -276,7 +300,7 @@ UIEXPORT void ui_button_args_set_labeltype(UiButtonArgs *args, int labeltype); UIEXPORT void ui_button_args_set_onclick(UiButtonArgs *args, ui_callback callback); UIEXPORT void ui_button_args_set_onclickdata(UiButtonArgs *args, void *onclickdata); -UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *groups); +UIEXPORT void ui_button_args_set_groups(UiButtonArgs *args, int *states, int numstates); UIEXPORT void ui_button_args_free(UiButtonArgs *args); UIEXPORT UiToggleArgs* ui_toggle_args_new(void); @@ -299,7 +323,7 @@ UIEXPORT void ui_toggle_args_set_varname(UiToggleArgs *args, const char *varname); UIEXPORT void ui_toggle_args_set_value(UiToggleArgs *args, UiInteger *value); UIEXPORT void ui_toggle_args_set_enablegroup(UiToggleArgs *args, int group); -UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *groups); +UIEXPORT void ui_toggle_args_set_groups(UiToggleArgs *args, int *states, int numstates); UIEXPORT void ui_toggle_args_free(UiToggleArgs *args); UIEXPORT UiLinkButtonArgs* ui_linkbutton_args_new(void); @@ -321,7 +345,7 @@ UIEXPORT void ui_linkbutton_args_set_onclickdata(UiLinkButtonArgs *args, void *userdata); UIEXPORT void ui_linkbutton_args_set_nofollow(UiLinkButtonArgs *args, UiBool value); UIEXPORT void ui_linkbutton_args_set_type(UiLinkButtonArgs *args, UiLinkType type); -UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *groups); +UIEXPORT void ui_linkbutton_args_set_groups(UiLinkButtonArgs *args, int *states, int numstates); UIEXPORT void ui_linkbutton_args_free(UiLinkButtonArgs *args); UIEXPORT UiListArgs* ui_list_args_new(void); @@ -342,6 +366,8 @@ UIEXPORT void ui_list_args_set_getvalue_func(UiListArgs *args, ui_getvaluefunc getvalue); UIEXPORT void ui_list_args_set_getvalue_func2(UiListArgs *args, ui_getvaluefunc2 getvalue); UIEXPORT void ui_list_args_set_getvalue_data(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_getstyle_func(UiListArgs *args, ui_getstylefunc getstyle); +UIEXPORT void ui_list_args_set_getstyle_data(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_onactivate(UiListArgs *args, ui_callback callback); UIEXPORT void ui_list_args_set_onactivatedata(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_onselection(UiListArgs *args, ui_callback callback); @@ -352,9 +378,11 @@ UIEXPORT void ui_list_args_set_ondragcompletedata(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_ondrop(UiListArgs *args, ui_callback callback); UIEXPORT void ui_list_args_set_ondropdata(UiListArgs *args, void *userdata); +UIEXPORT void ui_list_args_set_onsave(UiListArgs *args, ui_list_savefunc onsave); +UIEXPORT void ui_list_args_set_onsavedata(UiListArgs *args, void *userdata); UIEXPORT void ui_list_args_set_multiselection(UiListArgs *args, UiBool multiselection); UIEXPORT void ui_list_args_set_contextmenu(UiListArgs *args, UiMenuBuilder *menubuilder); -UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *groups); +UIEXPORT void ui_list_args_set_groups(UiListArgs *args, int *states, int numstates); UIEXPORT void ui_list_args_free(UiListArgs *args); UIEXPORT UiSourceListArgs* ui_sourcelist_args_new(void); @@ -377,6 +405,7 @@ UIEXPORT void ui_sourcelist_args_set_onactivatedata(UiSourceListArgs *args, void *userdata); UIEXPORT void ui_sourcelist_args_set_onbuttonclick(UiSourceListArgs *args, ui_callback callback); UIEXPORT void ui_sourcelist_args_set_onbuttonclickdata(UiSourceListArgs *args, void *userdata); +UIEXPORT void ui_sourcelist_args_set_contextmenu(UiSourceListArgs *args, UiMenuBuilder *menubuilder); UIEXPORT void ui_sourcelist_args_free(UiSourceListArgs *args); UIEXPORT UiTextAreaArgs* ui_textarea_args_new(void); @@ -394,7 +423,7 @@ UIEXPORT void ui_textarea_args_set_onchangedata(UiTextAreaArgs *args, void *onchangedata); UIEXPORT void ui_textarea_args_set_varname(UiTextAreaArgs *args, const char *varname); UIEXPORT void ui_textarea_args_set_value(UiTextAreaArgs *args, UiText *value); -UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *groups); +UIEXPORT void ui_textarea_args_set_groups(UiTextAreaArgs *args, int *states, int numstates); UIEXPORT void ui_textarea_args_free(UiTextAreaArgs *args); UIEXPORT UiTextFieldArgs* ui_textfield_args_new(void); @@ -414,9 +443,33 @@ UIEXPORT void ui_textfield_args_set_onactivatedata(UiTextFieldArgs *args, void *onactivatedata); UIEXPORT void ui_textfield_args_set_varname(UiTextFieldArgs *args, const char *varname); UIEXPORT void ui_textfield_args_set_value(UiTextFieldArgs *args, UiString *value); -UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *groups); +UIEXPORT void ui_textfield_args_set_groups(UiTextFieldArgs *args, int *states, int numstates); UIEXPORT void ui_textfield_args_free(UiTextFieldArgs *args); +UIEXPORT UiSpinBoxArgs* ui_spinbox_args_new(void); +UIEXPORT void ui_spinbox_args_set_fill(UiSpinBoxArgs *args, UiBool fill); +UIEXPORT void ui_spinbox_args_set_hexpand(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_vexpand(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_hfill(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_vfill(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_override_defaults(UiSpinBoxArgs *args, UiBool value); +UIEXPORT void ui_spinbox_args_set_colspan(UiSpinBoxArgs *args, int colspan); +UIEXPORT void ui_spinbox_args_set_rowspan(UiSpinBoxArgs *args, int rowspan); +UIEXPORT void ui_spinbox_args_set_name(UiSpinBoxArgs *args, const char *name); +UIEXPORT void ui_spinbox_args_set_style_class(UiSpinBoxArgs *args, const char *classname); +UIEXPORT void ui_spinbox_args_set_onchange(UiSpinBoxArgs *args, ui_callback callback); +UIEXPORT void ui_spinbox_args_set_onchangedata(UiSpinBoxArgs *args, void *onchangedata); +UIEXPORT void ui_spinbox_args_set_min(UiSpinBoxArgs *args, double min); +UIEXPORT void ui_spinbox_args_set_max(UiSpinBoxArgs *args, double max); +UIEXPORT void ui_spinbox_args_set_step(UiSpinBoxArgs *args, double step); +UIEXPORT void ui_spinbox_args_set_digits(UiSpinBoxArgs *args, int digits); +UIEXPORT void ui_spinbox_args_set_varname(UiSpinBoxArgs *args, const char *varname); +UIEXPORT void ui_spinbox_args_set_intvalue(UiSpinBoxArgs *args, UiInteger *value); +UIEXPORT void ui_spinbox_args_set_doublevalue(UiSpinBoxArgs *args, UiDouble *value); +UIEXPORT void ui_spinbox_args_set_rangevalue(UiSpinBoxArgs *args, UiRange *value); +UIEXPORT void ui_spinbox_args_set_groups(UiSpinBoxArgs *args, int *states, int numstates); +UIEXPORT void ui_spinbox_args_free(UiSpinBoxArgs *args); + UIEXPORT UiWebviewArgs* ui_webview_args_new(void); UIEXPORT void ui_webview_args_set_fill(UiWebviewArgs *args, UiBool fill); UIEXPORT void ui_webview_args_set_hexpand(UiWebviewArgs *args, UiBool value); @@ -430,7 +483,7 @@ UIEXPORT void ui_webview_args_set_style_class(UiWebviewArgs *args, const char *classname); UIEXPORT void ui_webview_args_set_varname(UiWebviewArgs *args, const char *varname); UIEXPORT void ui_webview_args_set_value(UiWebviewArgs *args, UiGeneric *value); -UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *groups); +UIEXPORT void ui_webview_args_set_groups(UiWebviewArgs *args, int *states, int numstates); UIEXPORT void ui_webview_args_free(UiWebviewArgs *args); #ifdef __cplusplus
--- a/ui/common/context.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/context.c Sat Oct 04 14:52:59 2025 +0200 @@ -38,6 +38,7 @@ #include "context.h" #include "../ui/window.h" +#include "../ui/widget.h" #include "document.h" #include "types.h" @@ -102,21 +103,19 @@ UiContext *doc_ctx = ui_document_context(document); doc_ctx->parent = ctx; - // check if any parent context has an unbound variable with the same name - // as any document variable + // if a document variable has the same name as a parent variable, + // move the bindings to the document UiContext *var_ctx = ctx; while(var_ctx) { - if(var_ctx->vars_unbound && cxMapSize(var_ctx->vars_unbound) > 0) { - CxMapIterator i = cxMapIterator(var_ctx->vars_unbound); - cx_foreach(CxMapEntry*, entry, i) { - printf("attach %s\n", entry->key->data); - UiVar *var = entry->value; - UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); - if(docvar) { - // bind var to document var - uic_copy_binding(var, docvar, TRUE); - cxIteratorFlagRemoval(i); - } + CxMapIterator i = cxMapIterator(var_ctx->vars); + cx_foreach(CxMapEntry*, entry, i) { + printf("attach %.*s\n", (int)entry->key->len, entry->key->data); + UiVar *var = entry->value; + UiVar *docvar = cxMapGet(doc_ctx->vars, *entry->key); + if(docvar) { + // bind var to document var + uic_copy_binding(var, docvar, TRUE); + cxIteratorFlagRemoval(i); } } @@ -129,10 +128,10 @@ cx_foreach(CxMapEntry*, entry, mi) { UiVar *var = entry->value; // var->from && var->from_ctx && var->from_ctx != ctx + uic_save_var(var); if(var->from) { - uic_save_var2(var); uic_copy_binding(var, var->from, FALSE); - cxMapPut(var->from->from_ctx->vars_unbound, *entry->key, var->from); + cxMapPut(var->from->from_ctx->vars, *entry->key, var->from); var->from = NULL; } } @@ -199,13 +198,6 @@ } UiVar* uic_create_var(UiContext *ctx, const char *name, UiVarType type) { - if(ctx->vars_unbound) { - UiVar *unbound = cxMapGet(ctx->vars_unbound, name); - if(unbound) { - return unbound; - } - } - UiVar *var = uic_get_var(ctx, name); if(var) { if(var->type == type) { @@ -224,10 +216,7 @@ cxMempoolSetDestructor(var, (cx_destructor_func)uic_unbind_var); - if(!ctx->vars_unbound) { - ctx->vars_unbound = cxHashMapCreate(ctx->allocator, CX_STORE_POINTERS, 16); - } - cxMapPut(ctx->vars_unbound, name, var); + cxMapPut(ctx->vars, name, var); return var; } @@ -278,7 +267,7 @@ } -UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type) { +UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type) { if (value) { return uic_create_value_var(current, value); } @@ -383,7 +372,7 @@ ui_setop_enable(FALSE); } -void uic_save_var2(UiVar *var) { +void uic_save_var(UiVar *var) { switch(var->type) { case UI_VAR_SPECIAL: break; case UI_VAR_INTEGER: uic_int_save(var->value); break; @@ -542,6 +531,9 @@ } void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...) { + if(enable == NULL) { + enable = (ui_enablefunc)ui_set_enabled; + } // get groups CxList *groups = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), 16); va_list ap; @@ -557,6 +549,22 @@ cxListFree(groups); } +void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups) { + if(enable == NULL) { + enable = (ui_enablefunc)ui_set_enabled; + } + CxList *ls = cxArrayListCreate(cxDefaultAllocator, NULL, sizeof(int), ngroups); + for(int i=0;i<ngroups;i++) { + cxListAdd(ls, groups+i); + } + uic_add_group_widget(ctx, widget, enable, ls); + cxListFree(ls); +} + +void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates) { + ui_widget_set_groups2(ctx, widget, (ui_enablefunc)ui_set_visible, states, nstates); +} + size_t uic_group_array_size(const int *groups) { int i; for(i=0;groups[i] >= 0;i++) { }
--- a/ui/common/context.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/context.h Sat Oct 04 14:52:59 2025 +0200 @@ -67,8 +67,7 @@ void *document; CxList *documents; - CxMap *vars; // manually created context vars - CxMap *vars_unbound; // unbound vars created by widgets + CxMap *vars; CxList *groups; // int list CxList *group_widgets; // UiGroupWidget list @@ -92,7 +91,6 @@ void *close_data; }; -// UiVar replacement, rename it to UiVar when finished struct UiVar { void *value; void *original_value; @@ -133,10 +131,10 @@ UiVar* uic_create_value_var(UiContext *ctx, void *value); void* uic_create_value(UiContext *ctx, UiVarType type); -UiVar* uic_widget_var(UiContext* toplevel, UiContext* current, void* value, const char* varname, UiVarType type); +UiVar* uic_widget_var(UiContext *toplevel, UiContext *current, void *value, const char *varname, UiVarType type); void uic_copy_binding(UiVar *from, UiVar *to, UiBool copytodoc); -void uic_save_var2(UiVar *var); +void uic_save_var(UiVar *var); void uic_unbind_var(UiVar *var); void uic_reg_var(UiContext *ctx, const char *name, UiVarType type, void *value);
--- a/ui/common/document.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/document.c Sat Oct 04 14:52:59 2025 +0200 @@ -36,22 +36,16 @@ #include <cx/mempool.h> -static CxMap *documents; -void uic_docmgr_init() { - if (!documents) { - documents = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); - } -} void* ui_document_new(size_t size) { CxMempool *mp = cxMempoolCreateSimple(256); const CxAllocator *a = mp->allocator; UiContext *ctx = uic_context(NULL, mp); - void *document = cxCalloc(a, size, 1); - cxMapPut(documents, cx_hash_key(&document, sizeof(void*)), ctx); - return document; + UiDoc *document = cxCalloc(a, sizeof(UiDoc) + size, 1); + document->ctx = ctx; + return &document->doc; } void ui_document_destroy(void *doc) { @@ -74,7 +68,9 @@ UiContext* ui_document_context(void *doc) { if(doc) { - return cxMapGet(documents, cx_hash_key(&doc, sizeof(void*))); + char *docPtr = doc; + UiDoc *document = (UiDoc*)(docPtr - sizeof(void*)); + return document->ctx; } else { return NULL; }
--- a/ui/common/document.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/document.h Sat Oct 04 14:52:59 2025 +0200 @@ -36,7 +36,11 @@ extern "C" { #endif -void uic_docmgr_init(); +typedef struct UiDoc { + UiContext *ctx; + char doc[]; +} UiDoc; + void uic_document_addvar(void *doc, char *name, int type, size_t vs);
--- a/ui/common/menu.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/menu.c Sat Oct 04 14:52:59 2025 +0200 @@ -100,7 +100,7 @@ menu->item.next = NULL; menu->item.type = UI_MENU; - menu->label = label; + menu->label = nl_strdup(label); menu->items_begin = NULL; menu->items_end = NULL; menu->parent = NULL; @@ -271,6 +271,7 @@ default: break; case UI_MENU: { UiMenu *menu = (UiMenu*)item; + free(menu->label); UiMenuItemI *m = menu->items_begin; while(m) { UiMenuItemI *next = m->next;
--- a/ui/common/menu.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/menu.h Sat Oct 04 14:52:59 2025 +0200 @@ -66,7 +66,7 @@ struct UiMenu { UiMenuItemI item; - const char *label; + char *label; UiMenuItemI *items_begin; UiMenuItemI *items_end; UiMenu *parent;
--- a/ui/common/object.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/object.c Sat Oct 04 14:52:59 2025 +0200 @@ -136,6 +136,7 @@ CxMempool *mp = cxMempoolCreateSimple(256); UiObject *obj = cxCalloc(mp->allocator, 1, sizeof(UiObject)); obj->ctx = uic_context(obj, mp); + obj->ctx->parent = ui_global_context(); uic_object_created(obj); return obj; }
--- a/ui/common/properties.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/properties.c Sat Oct 04 14:52:59 2025 +0200 @@ -73,7 +73,7 @@ #define UI_ENV_HOME "USERPROFILE" #endif -char* ui_configfile(char *name) { +char* ui_configfile(const char *name) { const char *appname = ui_appname(); if(!appname) { return NULL; @@ -128,6 +128,7 @@ void uic_load_app_properties() { application_properties = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 128); + application_properties->collection.simple_destructor = free; if(!ui_appname()) { // applications without name cannot load app properties @@ -202,7 +203,11 @@ } void ui_set_property(const char *name, const char *value) { - cxMapPut(application_properties, name, (char*)value); + if(value) { + cxMapPut(application_properties, name, strdup(value)); + } else { + cxMapRemove(application_properties, name); + } } const char* ui_set_default_property(const char *name, const char *value) { @@ -241,12 +246,14 @@ #ifndef UI_COCOA -void ui_locales_dir(char *path) { - locales_dir = path; +void ui_locales_dir(const char *path) { + free(locales_dir); + locales_dir = path ? strdup(path) : NULL; } -void ui_pixmaps_dir(char *path) { - pixmaps_dir = path; +void ui_pixmaps_dir(const char *path) { + free(pixmaps_dir); + pixmaps_dir = path ? strdup(path) : NULL; } char* uic_get_image_path(const char *imgfilename) { @@ -257,7 +264,7 @@ } } -void ui_load_lang(char *locale) { +void ui_load_lang(const char *locale) { if(!locale) { locale = "en_EN"; } @@ -319,12 +326,12 @@ return 0; } -char* uistr(char *name) { +char* uistr(const char *name) { char *value = uistr_n(name); return value ? value : "missing string"; } -char* uistr_n(char *name) { +char* uistr_n(const char *name) { if(!language) { return NULL; }
--- a/ui/common/toolbar.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/toolbar.c Sat Oct 04 14:52:59 2025 +0200 @@ -29,20 +29,21 @@ #include "toolbar.h" #include "menu.h" +#include <stdio.h> #include <string.h> static CxMap* toolbar_items; -static CxList* toolbar_defaults[3]; // 0: left 1: center 2: right +static CxList* toolbar_defaults[8]; // 0: left 1: center 2: right static UiToolbarMenuItem* ui_appmenu; void uic_toolbar_init(void) { toolbar_items = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); - toolbar_defaults[0] = cxLinkedListCreateSimple(CX_STORE_POINTERS); - toolbar_defaults[1] = cxLinkedListCreateSimple(CX_STORE_POINTERS); - toolbar_defaults[2] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + for(int i=0;i<8;i++) { + toolbar_defaults[i] = cxLinkedListCreateSimple(CX_STORE_POINTERS); + } } static char* nl_strdup(const char* str) { @@ -120,7 +121,7 @@ } CxList* uic_get_toolbar_defaults(enum UiToolbarPos pos) { - if (pos >= 0 && pos < 3) { + if (pos >= 0 && pos < 8) { return toolbar_defaults[pos]; } return NULL; @@ -128,15 +129,19 @@ void ui_toolbar_add_default(const char* name, enum UiToolbarPos pos) { char* cp = strdup(name); - if (pos >= 0 && pos < 3) { + if (pos >= 0 && pos < 8) { cxListAdd(toolbar_defaults[pos], cp); } else { - // TODO: error + fprintf(stderr, "Error: illegal toolbar position: %d\n", (int)pos); } } UiBool uic_toolbar_isenabled(void) { - return cxListSize(toolbar_defaults[0]) + cxListSize(toolbar_defaults[1]) + cxListSize(toolbar_defaults[2]) > 0; + size_t size = 0; + for(int i=0;i<8;i++) { + size += cxListSize(toolbar_defaults[i]); + } + return size > 0; } UiToolbarItemI* uic_toolbar_get_item(const char* name) {
--- a/ui/common/types.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/types.c Sat Oct 04 14:52:59 2025 +0200 @@ -220,6 +220,7 @@ va_end(ap); size_t len = cxListSize(cols); + info->alloc = len; info->columns = len; info->types = ui_calloc(ctx, len, sizeof(UiModelType)); info->titles = ui_calloc(ctx, len, sizeof(char*)); @@ -229,7 +230,7 @@ CxIterator iter = cxListIterator(cols); cx_foreach(UiColumn*, c, iter) { info->types[i] = c->type; - info->titles[i] = c->name; + info->titles[i] = ui_strdup(ctx, c->name); i++; } cxListFree(cols); @@ -237,6 +238,30 @@ return info; } +#define UI_MODEL_DEFAULT_ALLOC_SIZE 16 + +UiModel* ui_model_new(UiContext *ctx) { + UiModel *info = ui_calloc(ctx, 1, sizeof(UiModel)); + info->alloc = UI_MODEL_DEFAULT_ALLOC_SIZE; + info->types = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(UiModelType)); + info->titles = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(char*)); + info->columnsize = ui_calloc(ctx, UI_MODEL_DEFAULT_ALLOC_SIZE, sizeof(int)); + return info; +} + +void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width) { + if(model->columns >= model->alloc) { + model->alloc += UI_MODEL_DEFAULT_ALLOC_SIZE; + model->types = ui_realloc(ctx, model->types, model->alloc * sizeof(UiModelType)); + model->titles = ui_realloc(ctx, model->titles, model->alloc * sizeof(char*)); + model->columnsize = ui_realloc(ctx, model->columnsize, model->alloc * sizeof(int)); + } + model->types[model->columns] = type; + model->titles[model->columns] = ui_strdup(ctx, title); + model->columnsize[model->columns] = width; + model->columns++; +} + UiModel* ui_model_copy(UiContext *ctx, UiModel* model) { const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; @@ -258,6 +283,9 @@ void ui_model_free(UiContext *ctx, UiModel *mi) { const CxAllocator* a = ctx ? ctx->allocator : cxDefaultAllocator; + for(int i=0;i<mi->columns;i++) { + ui_free(ctx, mi->titles[i]); + } cxFree(a, mi->types); cxFree(a, mi->titles); cxFree(a, mi->columnsize); @@ -389,7 +417,7 @@ return s->get ? s->get(s) : s->value.ptr; } else { - return 0; + return NULL; } } @@ -419,6 +447,69 @@ return s->get ? s->get(s) : s->value.ptr; } else { + return NULL; + } +} + +void ui_range_set(UiRange *r, double value) { + if (r) { + if (r->set) { + r->set(r, value); + } else { + r->value = value; + } + } +} + +void ui_range_set_range(UiRange *r, double min, double max) { + if (r) { + if (r->setrange) { + r->setrange(r, min, max); + } else { + r->min = min; + r->max = max; + } + } +} + +void ui_range_set_extent(UiRange *r, double extent) { + if (r) { + if (r->setextent) { + r->setextent(r, extent); + } else { + r->extent = extent; + } + } +} + +double ui_range_get(UiRange *r) { + if (r) { + return r->get ? r->get(r) : r->value; + } else { + return 0; + } +} + +double ui_range_get_min(UiRange *r) { + if (r) { + return r->min; + } else { + return 0; + } +} + +double ui_range_get_max(UiRange *r) { + if (r) { + return r->max; + } else { + return 0; + } +} + +double ui_range_get_extent(UiRange *r) { + if (r) { + return r->extent; + } else { return 0; } } @@ -580,6 +671,8 @@ void uic_list_unbind(UiList *l) { l->update = NULL; + l->getselection = NULL; + l->setselection = NULL; l->obj = NULL; } @@ -599,10 +692,12 @@ } UIEXPORT void ui_list_setselection(UiList *list, int index) { - if (list->setselection && index >= 0) { - UiListSelection sel; - sel.count = 1; - sel.rows = &index; + if (list->setselection) { + UiListSelection sel = { 0, NULL }; + if(index >= 0) { + sel.count = 1; + sel.rows = &index; + } list->setselection(list, sel); } }
--- a/ui/common/wrapper.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/wrapper.c Sat Oct 04 14:52:59 2025 +0200 @@ -236,6 +236,15 @@ return sel->rows; } +UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num) { + UiListSelection sel; + sel.rows = indices; + sel.count = num; + if(list->setselection) { + list->setselection(list, sel); + } +} + void ui_list_selection_free(UiListSelection *sel) { ui_listselection_free(*sel); free(sel); @@ -253,3 +262,59 @@ } return NULL; } + +/* ---------------------------- UiTextStyle ---------------------------- */ + +void ui_textstyle_set_bold(UiTextStyle *style, UiBool set) { + if(set) { + style->text_style |= UI_TEXT_STYLE_BOLD; + } else { + style->text_style &= ~UI_TEXT_STYLE_BOLD; + } +} + +void ui_textstyle_set_underline(UiTextStyle *style, UiBool set) { + if(set) { + style->text_style |= UI_TEXT_STYLE_UNDERLINE; + } else { + style->text_style &= ~UI_TEXT_STYLE_UNDERLINE; + } +} + +void ui_textstyle_set_italic(UiTextStyle *style, UiBool set) { + if(set) { + style->text_style |= UI_TEXT_STYLE_ITALIC; + } else { + style->text_style &= ~UI_TEXT_STYLE_ITALIC; + } +} + +void ui_textstyle_set_color(UiTextStyle *style, int r, int g, int b) { + style->fg_set = TRUE; + style->fg.red = r; + style->fg.green = g; + style->fg.blue = b; +} + +void ui_textstyle_enable_color(UiTextStyle *style, UiBool enable) { + style->fg_set = enable; +} + + +/* ---------------------------- UiCellValue ---------------------------- */ + +UiBool ui_cell_value_is_string(UiCellValue *value) { + return value->type == UI_STRING_EDITABLE; +} + +UiBool ui_cell_value_is_int(UiCellValue *value) { + return FALSE; // TODO +} + +const char* ui_cell_value_get_string(UiCellValue *value) { + return value->string; +} + +int64_t ui_cell_value_get_int(UiCellValue *value) { + return value->i; +}
--- a/ui/common/wrapper.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/common/wrapper.h Sat Oct 04 14:52:59 2025 +0200 @@ -77,11 +77,23 @@ UIEXPORT UiListSelection* ui_list_get_selection_allocated(UiList *list); UIEXPORT int ui_list_selection_get_count(UiListSelection *sel); UIEXPORT int* ui_list_selection_get_rows(UiListSelection *sel); +UIEXPORT void ui_list_set_selected_indices(UiList *list, int *indices, int num); UIEXPORT void ui_list_selection_free(UiListSelection *sel); UIEXPORT int ui_filelist_count(UiFileList *flist); UIEXPORT char* ui_filelist_get(UiFileList *flist, int index); +UIEXPORT void ui_textstyle_set_bold(UiTextStyle *style, UiBool set); +UIEXPORT void ui_textstyle_set_underline(UiTextStyle *style, UiBool set); +UIEXPORT void ui_textstyle_set_italic(UiTextStyle *style, UiBool set); +UIEXPORT void ui_textstyle_set_color(UiTextStyle *style, int r, int g, int b); +UIEXPORT void ui_textstyle_enable_color(UiTextStyle *style, UiBool enable); + +UIEXPORT UiBool ui_cell_value_is_string(UiCellValue *value); +UIEXPORT UiBool ui_cell_value_is_int(UiCellValue *value); +UIEXPORT const char* ui_cell_value_get_string(UiCellValue *value); +UIEXPORT int64_t ui_cell_value_get_int(UiCellValue *value); + #ifdef __cplusplus }
--- a/ui/gtk/button.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/button.c Sat Oct 04 14:52:59 2025 +0200 @@ -382,13 +382,98 @@ } #endif + +#if GTK_MAJOR_VERSION >= 3 + +static void switch_changed( + GObject *gobject, + GParamSpec *pspec, + UiVarEventData *event) +{ + GtkSwitch *sw = GTK_SWITCH (gobject); + gboolean active = gtk_switch_get_active (sw); + + UiEvent e; + e.obj = event->obj; + e.document = e.obj->ctx->document; + e.window = e.obj->window; + e.eventdata = NULL; + e.eventdatatype = 0; + e.set = ui_get_setop(); + + if(event->callback) { + event->callback(&e, event->userdata); + } + if(event->var) { + UiInteger *i = event->var->value; + ui_notify_evt(i->observers, &e); + } +} + UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { -#ifdef UI_GTK3 - return NULL; // TODO + UiObject* current = uic_current_obj(obj); + GtkWidget *widget = gtk_switch_new(); + ui_set_name_and_style(widget, args->name, args->style_class); + ui_set_widget_groups(obj->ctx, widget, args->groups); + + UiVar* var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiInteger *value = var->value; + value->obj = widget; + value->get = ui_switch_get; + value->set = ui_switch_set; + + if(value->value) { + gtk_switch_set_active(GTK_SWITCH(widget), TRUE); + } + + + } + + UiVarEventData *event = malloc(sizeof(UiVarEventData)); + event->obj = obj; + event->callback = args->onchange; + event->userdata = args->onchangedata; + event->var = var; + event->observers = NULL; + + g_signal_connect( + widget, + "notify::active", + G_CALLBACK(switch_changed), + event); + + g_signal_connect( + widget, + "destroy", + G_CALLBACK(ui_destroy_vardata), + event); + + UI_APPLY_LAYOUT2(current, args); + current->container->add(current->container, widget); + + return widget; +} + +int64_t ui_switch_get(UiInteger *value) { + GtkSwitch *sw = GTK_SWITCH((GtkWidget*)value->obj); + value->value = gtk_switch_get_active(sw); + return value->value; +} + +void ui_switch_set(UiInteger *value, int64_t i) { + GtkSwitch *sw = GTK_SWITCH((GtkWidget*)value->obj); + value->value = i; + gtk_switch_set_active(sw, i); +} + #else + +UIWIDGET ui_switch_create(UiObject* obj, UiToggleArgs *args) { return ui_checkbox_create(obj, args); +} + #endif -} #if GTK_MAJOR_VERSION >= 4 #define RADIOBUTTON_NEW(group, label) gtk_check_button_new_with_label(label) @@ -760,6 +845,12 @@ data); } gtk_button_set_label(GTK_BUTTON(button), args->label); +#if GTK_CHECK_VERSION(4, 0, 0) + gtk_button_set_can_shrink(GTK_BUTTON(button), TRUE); +#elif GTK_MAJOR_VERSION == 3 + GtkWidget *child = gtk_bin_get_child(GTK_BIN(button)); + gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_END); +#endif g_object_set_data(G_OBJECT(button), "ui_linkbutton", data); g_signal_connect( button, @@ -806,6 +897,11 @@ if(s->value.free) { s->value.free(s->value.ptr); } +#if GTK_MAJOR_VERSION == 3 + UiLinkButton *data = s->obj; + GtkWidget *child = gtk_bin_get_child(GTK_BIN(data->widget)); + gtk_label_set_ellipsize(GTK_LABEL(child), PANGO_ELLIPSIZE_END); +#endif }
--- a/ui/gtk/button.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/button.h Sat Oct 04 14:52:59 2025 +0200 @@ -96,6 +96,9 @@ void ui_radio_obs(GtkToggleButton *widget, UiVarEventData *event); +int64_t ui_switch_get(UiInteger *value); +void ui_switch_set(UiInteger *value, int64_t i); + int64_t ui_radiobutton_get(UiInteger *value); void ui_radiobutton_set(UiInteger *value, int64_t i);
--- a/ui/gtk/container.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/container.c Sat Oct 04 14:52:59 2025 +0200 @@ -28,6 +28,7 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <limits.h> #include "container.h" @@ -37,6 +38,8 @@ #include "../common/context.h" #include "../common/object.h" +#include "../ui/properties.h" + void ui_container_begin_close(UiObject *obj) { UiContainer *ct = uic_get_current_container(obj); @@ -193,14 +196,14 @@ if(!ct->layout.override_defaults) { if(grid->def_hexpand) { hexpand = TRUE; - hfill = TRUE; - } else if(grid->def_hfill) { + } + if(grid->def_hfill) { hfill = TRUE; } if(grid->def_vexpand) { vexpand = TRUE; - vfill = TRUE; - } else if(grid->def_vfill) { + } + if(grid->def_vfill) { vfill = TRUE; } } @@ -208,19 +211,21 @@ UiBool fill = ct->layout.fill; if(ct->layout.hexpand) { hexpand = TRUE; - hfill = TRUE; - } else if(ct->layout.hfill) { + } + if(ct->layout.hfill) { hfill = TRUE; } if(ct->layout.vexpand) { vexpand = TRUE; - vfill = TRUE; - } else if(ct->layout.vfill) { + } + if(ct->layout.vfill) { vfill = TRUE; } if(fill) { hfill = TRUE; vfill = TRUE; + hexpand = TRUE; + vexpand = TRUE; } if(!hfill) { @@ -1048,6 +1053,35 @@ } #endif +/* ------------------------ Split Window Panels ------------------------ */ + +static UIWIDGET splitwindow_panel(UiObject *obj, GtkWidget *pbox, UiSidebarArgs *args) { + if(!pbox) { + fprintf(stderr, "Error: window is not a splitview window\n"); + return NULL; + } + + GtkWidget *box = ui_gtk_vbox_new(args->spacing); + ui_set_name_and_style(box, args->name, args->style_class); + ui_box_set_margin(box, args->margin); + BOX_ADD_EXPAND(pbox, box); + + UiObject *newobj = uic_object_new(obj, box); + newobj->container = ui_box_container(obj, box, UI_CONTAINER_VBOX); + uic_obj_add(obj, newobj); + + return box; +} + +UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args) { + return splitwindow_panel(obj, g_object_get_data(G_OBJECT(obj->widget), "ui_left_panel"), args); +} + +UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args) { + return splitwindow_panel(obj, g_object_get_data(G_OBJECT(obj->widget), "ui_right_panel"), args); +} + + /* -------------------- Splitpane -------------------- */ static GtkWidget* create_paned(UiOrientation orientation) { @@ -1065,7 +1099,13 @@ return NULL; } - +static void save_pane_pos(GtkWidget *widget, char *property_name) { + int pos = gtk_paned_get_position(GTK_PANED(widget)); + char buf[32]; + snprintf(buf, 32, "%d", pos); + ui_set_property(property_name, buf); + free(property_name); +} static UIWIDGET splitpane_create(UiObject *obj, UiOrientation orientation, UiSplitPaneArgs *args) { UiObject* current = uic_current_obj(obj); @@ -1077,12 +1117,40 @@ int max = args->max_panes == 0 ? 2 : args->max_panes; + if(args->position_property) { + const char *pos_str = ui_get_property(args->position_property); + if(pos_str) { + char *end; + long pos = strtol(pos_str, &end, 10); + if(*end == '\0') { + args->initial_position = (int)pos; + } + } + + g_signal_connect( + pane0, + "destroy", + G_CALLBACK(save_pane_pos), + strdup(args->position_property)); + } + UiObject *newobj = uic_object_new(obj, pane0); newobj->container = ui_splitpane_container(obj, pane0, orientation, max, args->initial_position); uic_obj_add(obj, newobj); g_object_set_data(G_OBJECT(pane0), "ui_splitpane", newobj->container); + UiVar *var = uic_widget_var(obj->ctx, current->ctx, args->value, args->varname, UI_VAR_INTEGER); + if(var) { + UiSplitPaneContainer *s = (UiSplitPaneContainer*)newobj->container; + UiInteger *i = var->value; + s->initial_position = i->value; + + i->obj = s; + i->get = ui_splitpane_get; + i->set = ui_splitpane_set; + } + return pane0; } @@ -1138,6 +1206,18 @@ } } +int64_t ui_splitpane_get(UiInteger *i) { + UiSplitPaneContainer *s = i->obj; + i->value = gtk_paned_get_position(GTK_PANED(s->container.widget)); + return i->value; +} + +void ui_splitpane_set(UiInteger *i, int64_t value) { + UiSplitPaneContainer *s = i->obj; + i->value = value; + gtk_paned_set_position(GTK_PANED(s->container.widget), (int)value); +} + UIEXPORT void ui_splitpane_set_visible(UIWIDGET splitpane, int child_index, UiBool visible) { UiSplitPaneContainer *ct = g_object_get_data(G_OBJECT(splitpane), "ui_splitpane"); if(!ct) {
--- a/ui/gtk/container.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/container.h Sat Oct 04 14:52:59 2025 +0200 @@ -197,6 +197,8 @@ UiContainer* ui_splitpane_container(UiObject *obj, GtkWidget *pane, UiOrientation orientation, int max, int init); void ui_splitpane_container_add(UiContainer *ct, GtkWidget *widget); +int64_t ui_splitpane_get(UiInteger *i); +void ui_splitpane_set(UiInteger *i, int64_t value); UiGtkTabView* ui_widget_get_tabview_data(UIWIDGET tabview);
--- a/ui/gtk/entry.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/entry.c Sat Oct 04 14:52:59 2025 +0200 @@ -35,28 +35,38 @@ #include "entry.h" -UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs *args) { - double min = 0; - double max = 1000; +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { + double min = args->min; + double max = args->max != 0 ? args->max : 1000; UiObject* current = uic_current_obj(obj); UiVar *var = NULL; + UiVarType vartype = 0; if(args->varname) { var = uic_get_var(obj->ctx, args->varname); + if(var) { + vartype = var->type; + } else { + var = uic_widget_var(obj->ctx, current->ctx, args->rangevalue, args->varname, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; + } } if(!var) { if(args->intvalue) { var = uic_widget_var(obj->ctx, current->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; } else if(args->doublevalue) { var = uic_widget_var(obj->ctx, current->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; } else if(args->rangevalue) { var = uic_widget_var(obj->ctx, current->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; } } - if(var && var->type == UI_VAR_RANGE) { + if(vartype == UI_VAR_RANGE) { UiRange *r = var->value; min = r->min; max = r->max; @@ -72,11 +82,16 @@ GtkWidget *spin = gtk_spin_button_new_with_range(min, max, args->step); ui_set_name_and_style(spin, args->name, args->style_class); ui_set_widget_groups(obj->ctx, spin, args->groups); + + if(args->width > 0) { + gtk_widget_set_size_request(spin, args->width, -1); + } + gtk_spin_button_set_digits(GTK_SPIN_BUTTON(spin), args->digits); UiObserver **obs = NULL; if(var) { double value = 0; - switch(var->type) { + switch(vartype) { default: break; case UI_VAR_INTEGER: { UiInteger *i = var->value;
--- a/ui/gtk/headerbar.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/headerbar.c Sat Oct 04 14:52:59 2025 +0200 @@ -31,21 +31,70 @@ #include "button.h" #include "menu.h" +#include "../ui/properties.h" + #if GTK_CHECK_VERSION(3, 10, 0) -void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar) { +void ui_fill_headerbar(UiObject *obj, GtkWidget *sidebar_headerbar, GtkWidget *main_headerbar, GtkWidget *right_headerbar) { + CxList *sidebar_left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_SIDEBAR_LEFT); + CxList *sidebar_right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_SIDEBAR_RIGHT); + CxList *left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_LEFT); CxList *center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_CENTER); CxList *right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHT); - ui_headerbar_add_items(obj, headerbar, left_defaults, UI_TOOLBAR_LEFT); - ui_headerbar_add_items(obj, headerbar, center_defaults, UI_TOOLBAR_CENTER); - + CxList *rightpanel_left_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_LEFT); + CxList *rightpanel_center_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_CENTER); + CxList *rightpanel_right_defaults = uic_get_toolbar_defaults(UI_TOOLBAR_RIGHTPANEL_RIGHT); + UiToolbarMenuItem *appmenu = uic_get_appmenu(); - if(appmenu) { - ui_add_headerbar_menu(headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + const char *appmenu_pos_str = ui_get_property("ui.gtk.window.appmenu.position"); + int appmenu_pos = UI_TOOLBAR_RIGHT; + if(sidebar_headerbar) { + appmenu_pos = UI_TOOLBAR_SIDEBAR_RIGHT; + } else if(right_headerbar) { + appmenu_pos = UI_TOOLBAR_RIGHTPANEL_RIGHT; + } + if(appmenu_pos_str) { + if(!strcmp(appmenu_pos_str, "sidebar") && sidebar_headerbar) { + appmenu_pos = UI_TOOLBAR_SIDEBAR_RIGHT; + } else if(!strcmp(appmenu_pos_str, "main")) { + appmenu_pos = UI_TOOLBAR_RIGHT; + } else if(!strcmp(appmenu_pos_str, "rightpanel") && right_headerbar) { + appmenu_pos = UI_TOOLBAR_RIGHTPANEL_RIGHT; + } + } + + // main toolbar + ui_headerbar_add_items(obj, main_headerbar, left_defaults, UI_TOOLBAR_LEFT); + ui_headerbar_add_items(obj, main_headerbar, center_defaults, UI_TOOLBAR_CENTER); + + if(appmenu && appmenu_pos == UI_TOOLBAR_RIGHT) { + ui_add_headerbar_menu(main_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); } - ui_headerbar_add_items(obj, headerbar, right_defaults, UI_TOOLBAR_RIGHT); + ui_headerbar_add_items(obj, main_headerbar, right_defaults, UI_TOOLBAR_RIGHT); + + // sidebar + if(sidebar_headerbar) { + // ui_headerbar_add_items pos parameter uses only UI_TOOLBAR_LEFT, UI_TOOLBAR_CENTER, UI_TOOLBAR_RIGHT + ui_headerbar_add_items(obj, sidebar_headerbar, sidebar_left_defaults, UI_TOOLBAR_LEFT); + + if(appmenu && appmenu_pos == UI_TOOLBAR_SIDEBAR_RIGHT) { + ui_add_headerbar_menu(sidebar_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, sidebar_headerbar, sidebar_right_defaults, UI_TOOLBAR_RIGHT); + } + + // right panel + if(right_headerbar) { + ui_headerbar_add_items(obj, right_headerbar, rightpanel_left_defaults, UI_TOOLBAR_LEFT); + ui_headerbar_add_items(obj, right_headerbar, rightpanel_center_defaults, UI_TOOLBAR_CENTER); + + if(appmenu && appmenu_pos == UI_TOOLBAR_RIGHTPANEL_RIGHT) { + ui_add_headerbar_menu(right_headerbar, NULL, appmenu, obj, UI_TOOLBAR_RIGHT); + } + ui_headerbar_add_items(obj, right_headerbar, rightpanel_right_defaults, UI_TOOLBAR_RIGHT); + } } static void create_item(UiObject *obj, GtkWidget *headerbar, GtkWidget *box, UiToolbarItemI *i, enum UiToolbarPos pos) {
--- a/ui/gtk/headerbar.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/headerbar.h Sat Oct 04 14:52:59 2025 +0200 @@ -58,7 +58,7 @@ #endif #endif -void ui_fill_headerbar(UiObject *obj, GtkWidget *headerbar); +void ui_fill_headerbar(UiObject *obj, GtkWidget *sidebar_headerbar, GtkWidget *main_headerbar, GtkWidget *right_headerbar); void ui_headerbar_add_items(UiObject *obj, GtkWidget *headerbar, CxList *items, enum UiToolbarPos pos);
--- a/ui/gtk/list.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/list.c Sat Oct 04 14:52:59 2025 +0200 @@ -92,6 +92,11 @@ tableview->ondropdata = args->ondropdata; tableview->selection.count = 0; tableview->selection.rows = NULL; + tableview->current_row = -1; + tableview->getstyle = args->getstyle; + tableview->getstyledata = args->getstyledata; + tableview->onsave = args->onsave; + tableview->onsavedata = args->onsavedata; if(args->getvalue2) { tableview->getvalue = args->getvalue2; @@ -102,7 +107,7 @@ } else { tableview->getvalue = null_getvalue; } - + return tableview; } @@ -140,6 +145,58 @@ /* END GObject wrapper for generic pointers */ +typedef struct UiCellEntry { + GtkEntry *entry; + UiListView *listview; + char *previous_value; + int row; + int col; +} UiCellEntry; + +static void cell_save_value(UiCellEntry *data, int restore) { + if(data->listview && data->listview->onsave) { + UiVar *var = data->listview->var; + UiList *list = var ? var->value : NULL; + const char *str = ENTRY_GET_TEXT(data->entry); + UiCellValue value; + value.string = str; + value.type = UI_STRING_EDITABLE; + if(data->listview->onsave(list, data->row, data->col, &value, data->listview->onsavedata)) { + free(data->previous_value); + data->previous_value = strdup(str); + } else if(restore) { + ENTRY_SET_TEXT(data->entry, data->previous_value); + } + } +} + +static void cell_entry_leave_focus( + GtkEventControllerFocus *self, + UiCellEntry *data) +{ + // TODO: use a different singal to track focus + // we only want to call cell_save_value, when another entry is selected, + // not when the window loses focus or something like that + cell_save_value(data, TRUE); +} + +static void cell_entry_destroy(GtkWidget *object, UiCellEntry *data) { + free(data->previous_value); + free(data); +} + +static void cell_entry_unmap(GtkWidget *w, UiCellEntry *data) { + const char *text = ENTRY_GET_TEXT(w); + cell_save_value(data, FALSE); +} + +static void cell_entry_activate( + GtkEntry *self, + UiCellEntry *data) +{ + cell_save_value(data, TRUE); +} + static void column_factory_setup(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { UiColData *col = userdata; UiModel *model = col->listview->model; @@ -156,6 +213,38 @@ } else if(type == UI_ICON) { GtkWidget *image = gtk_image_new(); gtk_list_item_set_child(item, image); + } else if(type == UI_STRING_EDITABLE) { + GtkWidget *textfield = gtk_entry_new(); + gtk_widget_add_css_class(textfield, "ui-table-entry"); + gtk_list_item_set_child(item, textfield); + + UiCellEntry *entry_data = malloc(sizeof(UiCellEntry)); + entry_data->entry = GTK_ENTRY(textfield); + entry_data->listview = NULL; + entry_data->previous_value = NULL; + entry_data->col = 0; + entry_data->row = 0; + g_object_set_data(G_OBJECT(textfield), "ui_entry_data", entry_data); + + g_signal_connect( + textfield, + "destroy", + G_CALLBACK(cell_entry_destroy), + entry_data); + g_signal_connect( + textfield, + "activate", + G_CALLBACK(cell_entry_activate), + entry_data); + g_signal_connect( + textfield, + "unmap", + G_CALLBACK(cell_entry_unmap), + entry_data); + + GtkEventController *focus_controller = gtk_event_controller_focus_new(); + g_signal_connect(focus_controller, "leave", G_CALLBACK(cell_entry_leave_focus), entry_data); + gtk_widget_add_controller(textfield, focus_controller); } else { GtkWidget *label = gtk_label_new(NULL); gtk_label_set_xalign(GTK_LABEL(label), 0); @@ -163,19 +252,83 @@ } } -static void column_factory_bind(GtkListItemFactory *factory, GtkListItem *item, gpointer userdata) { +PangoAttrList* textstyle2pangoattributes(UiTextStyle style) { + PangoAttrList *attr = pango_attr_list_new(); + + if(style.text_style & UI_TEXT_STYLE_BOLD) { + pango_attr_list_insert(attr, pango_attr_weight_new(PANGO_WEIGHT_BOLD)); + } + if(style.text_style & UI_TEXT_STYLE_ITALIC) { + pango_attr_list_insert(attr, pango_attr_style_new(PANGO_STYLE_ITALIC)); + } + if(style.text_style & UI_TEXT_STYLE_UNDERLINE) { + pango_attr_list_insert(attr, pango_attr_underline_new(PANGO_UNDERLINE_SINGLE)); + } + + // foreground color, convert from 8bit to 16bit + guint16 r = (guint16)style.fg.red * 257; + guint16 g = (guint16)style.fg.green * 257; + guint16 b = (guint16)style.fg.blue * 257; + pango_attr_list_insert(attr, pango_attr_foreground_new(r, g, b)); + + return attr; +} + +static void column_factory_bind(GtkListItemFactory *unused, GtkListItem *item, gpointer userdata) { UiColData *col = userdata; UiList *list = col->listview->var ? col->listview->var->value : NULL; UiListView *listview = col->listview; - + ObjWrapper *obj = gtk_list_item_get_item(item); UiModel *model = col->listview->model; UiModelType type = model->types[col->model_column]; + // cache the GtkListItem + CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); + UiRowItems *row = cxMapGet(listview->bound_rows, row_key); + if(row) { + if(row->items[col->model_column] == NULL) { + row->bound++; + } + } else { + row = calloc(1, sizeof(UiRowItems) + listview->numcolumns * sizeof(GtkListItem*)); + cxMapPut(listview->bound_rows, row_key, row); + row->bound = 1; + } + row->items[col->model_column] = item; + UiBool freevalue = FALSE; void *data = listview->getvalue(list, obj->data, obj->i, col->data_column, listview->getvaluedata, &freevalue); GtkWidget *child = gtk_list_item_get_child(item); + PangoAttrList *attributes = NULL; + UiTextStyle style = { 0, 0 }; + if(listview->getstyle) { + // query current row style, if it wasn't already queried + if(obj->i != listview->current_row) { + listview->current_row = obj->i; + listview->row_style = (UiTextStyle){ 0, 0 }; + listview->apply_row_style = listview->getstyle(list, obj->data, obj->i, -1, listview->getstyledata, &listview->row_style); + style = listview->row_style; + if(listview->apply_row_style) { + pango_attr_list_unref(listview->current_row_attributes); + listview->current_row_attributes = textstyle2pangoattributes(style); + } + } + + int style_col = col->data_column; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + style_col++; // col->data_column is the icon, we need the next col for the label + } + + // get the column style + if(listview->getstyle(list, obj->data, obj->i, style_col, listview->getstyledata, &style)) { + attributes = textstyle2pangoattributes(style); + } else if(listview->apply_row_style) { + attributes = listview->current_row_attributes; + } + } + switch(type) { case UI_STRING_FREE: { freevalue = TRUE; @@ -185,6 +338,7 @@ if(freevalue) { free(data); } + gtk_label_set_attributes(GTK_LABEL(child), attributes); break; } case UI_INTEGER: { @@ -192,6 +346,7 @@ char buf[32]; snprintf(buf, 32, "%d", (int)intvalue); gtk_label_set_label(GTK_LABEL(child), buf); + gtk_label_set_attributes(GTK_LABEL(child), attributes); break; } case UI_ICON: { @@ -217,15 +372,55 @@ } if(data2 && label) { gtk_label_set_label(GTK_LABEL(label), data2); + gtk_label_set_attributes(GTK_LABEL(label), attributes); } if(freevalue) { free(data2); } break; } + case UI_STRING_EDITABLE: { + UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); + if(entry) { + entry->listview = col->listview; + entry->row = obj->i; + entry->col = col->data_column; + entry->previous_value = strdup(data); + } + ENTRY_SET_TEXT(child, data); + break; + } + } + + if(attributes != listview->current_row_attributes) { + pango_attr_list_unref(attributes); } } +static void column_factory_unbind(GtkSignalListItemFactory *self, GtkListItem *item, UiColData *col) { + ObjWrapper *obj = gtk_list_item_get_item(item); + UiListView *listview = col->listview; + CxHashKey row_key = cx_hash_key(&obj->i, sizeof(int)); + UiRowItems *row = cxMapGet(listview->bound_rows, row_key); + if(row) { + row->items[col->model_column] = NULL; + row->bound--; + if(row->bound == 0) { + cxMapRemove(listview->bound_rows, row_key); + } + } // else: should not happen + + GtkWidget *child = gtk_list_item_get_child(item); + UiCellEntry *entry = g_object_get_data(G_OBJECT(child), "ui_entry_data"); + if(entry) { + cell_save_value(entry, FALSE); + entry->listview = NULL; + free(entry->previous_value); + entry->previous_value = NULL; + } +} + + static GtkSelectionModel* create_selection_model(UiListView *listview, GListStore *liststore, bool multiselection) { GtkSelectionModel *selection_model; if(multiselection) { @@ -253,10 +448,14 @@ listview->getvalue = str_getvalue; } + listview->numcolumns = 1; listview->columns = malloc(sizeof(UiColData)); listview->columns->listview = listview; listview->columns->data_column = 0; listview->columns->model_column = 0; + + listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); + listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); @@ -339,11 +538,15 @@ listview->getvalue = str_getvalue; } + listview->numcolumns = 1; listview->columns = malloc(sizeof(UiColData)); listview->columns->listview = listview; listview->columns->data_column = 0; listview->columns->model_column = 0; + listview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); + listview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; + GtkListItemFactory *factory = gtk_signal_list_item_factory_new(); g_signal_connect(factory, "setup", G_CALLBACK(column_factory_setup), listview->columns); g_signal_connect(factory, "bind", G_CALLBACK(column_factory_bind), listview->columns); @@ -432,6 +635,10 @@ int columns = model ? model->columns : 0; tableview->columns = calloc(columns, sizeof(UiColData)); + tableview->numcolumns = columns; + + tableview->bound_rows = cxHashMapCreate(NULL, CX_STORE_POINTERS, 128); + tableview->bound_rows->collection.simple_destructor = (cx_destructor_func)free; int addi = 0; for(int i=0;i<columns;i++) { @@ -631,21 +838,20 @@ } else { void *value = list->get(list, i); if(value) { - ObjWrapper *obj = obj_wrapper_new(value, i); - UiListSelection sel = list->getselection(list); - // TODO: if index i is selected, the selection is lost - // is it possible to update the item without removing it? - // workaround: save selection and reapply it - int count = g_list_model_get_n_items(G_LIST_MODEL(view->liststore)); - if(count <= i) { - g_list_store_splice(view->liststore, i, 0, (void **)&obj, 1); - } else { - g_list_store_splice(view->liststore, i, 1, (void **)&obj, 1); + ObjWrapper *obj = g_list_model_get_item(G_LIST_MODEL(view->liststore), i); + if(obj) { + obj->data = value; } - if(sel.count > 0) { - list->setselection(list, sel); + + CxHashKey row_key = cx_hash_key(&i, sizeof(int)); + UiRowItems *row = cxMapGet(view->bound_rows, row_key); + if(row) { + for(int c=0;c<view->numcolumns;c++) { + if(row->items[c] != NULL) { + column_factory_bind(NULL, row->items[c], &view->columns[c]); + } + } } - ui_listselection_free(sel); } } } @@ -709,14 +915,40 @@ static void update_list_row(UiListView *listview, GtkListStore *store, GtkTreeIter *iter, UiList *list, void *elm, int row) { UiModel *model = listview->model; + ui_getstylefunc getstyle = listview->getstyle; + + // get the row style + UiBool style_set = FALSE; + UiTextStyle style = { 0, 0 }; + if(getstyle) { + style_set = getstyle(list, elm, row, -1, listview->getstyledata, &style); + } + // set column values int c = 0; for(int i=0;i<model->columns;i++,c++) { UiBool freevalue = FALSE; void *data = listview->getvalue(list, elm, row, c, listview->getvaluedata, &freevalue); + + UiModelType type = model->types[i]; + + if(getstyle) { + // in case the column is icon+text, only get a style for the text column + int style_col = c; + if(type == UI_ICON_TEXT || type == UI_ICON_TEXT_FREE) { + style_col++; + } + + // Get the individual column style + // The column style overrides the row style, however if no column style + // is provided, we stick with the row style + if(getstyle(list, elm, row, style_col, listview->getstyledata, &style)) { + style_set = TRUE; + } + } GValue value = G_VALUE_INIT; - switch(model->types[i]) { + switch(type) { case UI_STRING_FREE: { freevalue = TRUE; } @@ -789,13 +1021,64 @@ } gtk_list_store_set_value(store, iter, c, &value); + + if(style_set) { + int soff = listview->style_offset + i*6; + + GValue style_set_value = G_VALUE_INIT; + g_value_init(&style_set_value, G_TYPE_BOOLEAN); + g_value_set_boolean(&style_set_value, TRUE); + gtk_list_store_set_value(store, iter, soff, &style_set_value); + + GValue style_weight_value = G_VALUE_INIT; + g_value_init(&style_weight_value, G_TYPE_INT); + if(style.text_style & UI_TEXT_STYLE_BOLD) { + g_value_set_int(&style_weight_value, 600); + } else { + g_value_set_int(&style_weight_value, 400); + } + gtk_list_store_set_value(store, iter, soff + 1, &style_weight_value); + + GValue style_underline_value = G_VALUE_INIT; + g_value_init(&style_underline_value, G_TYPE_INT); + if(style.text_style & UI_TEXT_STYLE_UNDERLINE) { + g_value_set_int(&style_underline_value, PANGO_UNDERLINE_SINGLE); + } else { + g_value_set_int(&style_underline_value, PANGO_UNDERLINE_NONE); + } + gtk_list_store_set_value(store, iter, soff + 2, &style_underline_value); + + GValue style_italic_value = G_VALUE_INIT; + g_value_init(&style_italic_value, G_TYPE_INT); + if(style.text_style & UI_TEXT_STYLE_ITALIC) { + g_value_set_int(&style_italic_value, PANGO_STYLE_ITALIC); + } else { + g_value_set_int(&style_italic_value, PANGO_STYLE_NORMAL); + } + gtk_list_store_set_value(store, iter, soff + 3, &style_italic_value); + + GValue style_fgset_value = G_VALUE_INIT; + g_value_init(&style_fgset_value, G_TYPE_BOOLEAN); + g_value_set_boolean(&style_fgset_value, style.fg_set); + gtk_list_store_set_value(store, iter, soff + 4, &style_fgset_value); + + if(style.fg_set) { + char buf[8]; + snprintf(buf, 8, "#%02X%02X%02X", (int)style.fg.red, (int)style.fg.green, (int)style.fg.blue); + + GValue style_fg_value = G_VALUE_INIT; + g_value_init(&style_fg_value, G_TYPE_STRING); + g_value_set_string(&style_fg_value, buf); + gtk_list_store_set_value(store, iter, soff + 5, &style_fg_value); + } + } } } static GtkListStore* create_list_store(UiListView *listview, UiList *list) { UiModel *model = listview->model; int columns = model->columns; - GType types[2*columns]; + GType *types = calloc(columns*8, sizeof(GType)); int c = 0; for(int i=0;i<columns;i++,c++) { switch(model->types[i]) { @@ -810,8 +1093,18 @@ } } } + int s = 0; + for(int i=0;i<columns;i++) { + types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // *-set + types[listview->style_offset+s] = G_TYPE_INT; s++; // weight + types[listview->style_offset+s] = G_TYPE_INT; s++; // underline + types[listview->style_offset+s] = G_TYPE_INT; s++; // style + types[listview->style_offset+s] = G_TYPE_BOOLEAN; s++; // foreground-set + types[listview->style_offset+s] = G_TYPE_STRING; s++; // foreground + } - GtkListStore *store = gtk_list_store_newv(c, types); + GtkListStore *store = gtk_list_store_newv(c+s, types); + free(types); if(list) { void *elm = list->first(list); @@ -857,6 +1150,7 @@ UiModel *model = ui_model(obj->ctx, UI_STRING, "", -1); UiListView *listview = create_listview(obj, args); + listview->style_offset = 1; if(!args->getvalue && !args->getvalue2) { listview->getvalue = str_getvalue; } @@ -957,10 +1251,22 @@ UiModel *model = args->model; int columns = model ? model->columns : 0; + // find the last data column index int addi = 0; - for(int i=0;i<columns;i++) { + int style_offset = 0; + int i = 0; + for(;i<columns;i++) { + if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { + addi++; + } + } + style_offset = i+addi; + + // create columns and init cell renderers + addi = 0; + for(i=0;i<columns;i++) { GtkTreeViewColumn *column = NULL; - if(model->types[i] == UI_ICON_TEXT) { + if(model->types[i] == UI_ICON_TEXT || model->types[i] == UI_ICON_TEXT_FREE) { column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, model->titles[i]); @@ -971,8 +1277,21 @@ gtk_tree_view_column_pack_start(column, iconrenderer, FALSE); - gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", i); - gtk_tree_view_column_add_attribute(column, textrenderer, "text", i+1); + gtk_tree_view_column_add_attribute(column, iconrenderer, "pixbuf", addi + i); + gtk_tree_view_column_add_attribute(column, textrenderer, "text", addi + i+1); + + if(args->getstyle) { + int soff = style_offset + i*6; + gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff); + + gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2); + gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5); + } addi++; } else if (model->types[i] == UI_ICON) { @@ -984,13 +1303,26 @@ i + addi, NULL); } else { - GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); + GtkCellRenderer *textrenderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( model->titles[i], - renderer, + textrenderer, "text", i + addi, NULL); + + if(args->getstyle) { + int soff = style_offset + i*6; + gtk_tree_view_column_add_attribute(column, textrenderer, "weight-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline-set", soff); + gtk_tree_view_column_add_attribute(column, textrenderer, "style-set", soff); + + gtk_tree_view_column_add_attribute(column, textrenderer, "weight", soff + 1); + gtk_tree_view_column_add_attribute(column, textrenderer, "underline", soff + 2); + gtk_tree_view_column_add_attribute(column, textrenderer, "style", soff + 3); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground-set", soff + 4); + gtk_tree_view_column_add_attribute(column, textrenderer, "foreground", soff + 5); + } } int colsz = model->columnsize[i]; @@ -1019,6 +1351,8 @@ // add TreeView as observer to the UiList to update the TreeView if the // data changes UiListView *tableview = create_listview(obj, args); + tableview->widget = view; + tableview->style_offset = style_offset; g_signal_connect( view, "destroy", @@ -1154,6 +1488,7 @@ UiListView *listview = create_listview(obj, args); listview->widget = combobox; + listview->style_offset = 1; listview->model = ui_model(obj->ctx, UI_STRING, "", -1); g_signal_connect( combobox, @@ -1676,6 +2011,8 @@ } #if GTK_CHECK_VERSION(4, 10, 0) free(v->columns); + pango_attr_list_unref(v->current_row_attributes); + cxMapFree(v->bound_rows); #endif free(v->selection.rows); free(v); @@ -1947,7 +2284,7 @@ } } -static GtkWidget* create_listbox_row(UiListBox *listbox, UiListBoxSubList *sublist, UiSubListItem *item, int index) { +static void listbox_fill_row(UiListBox *listbox, GtkWidget *row, UiListBoxSubList *sublist, UiSubListItem *item, int index) { GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); if(item->icon) { GtkWidget *icon = ICON_IMAGE(item->icon); @@ -1959,7 +2296,6 @@ if(item->badge) { } - GtkWidget *row = gtk_list_box_row_new(); LISTBOX_ROW_SET_CHILD(row, hbox); // signals @@ -1987,7 +2323,7 @@ if(item->badge) { GtkWidget *badge = gtk_label_new(item->badge); WIDGET_ADD_CSS_CLASS(badge, "ui-badge"); -#if GTK_CHECK_VERSION(4, 0, 0) +#if GTK_CHECK_VERSION(3, 14, 0) gtk_widget_set_valign(badge, GTK_ALIGN_CENTER); BOX_ADD(hbox, badge); #else @@ -2010,8 +2346,36 @@ event ); } +} + +static void update_sublist_item(UiListBox *listbox, UiListBoxSubList *sublist, int index) { + GtkListBoxRow *row = gtk_list_box_get_row_at_index(listbox->listbox, sublist->startpos + index); + if(!row) { + return; + } + UiList *list = sublist->var->value; + if(!list) { + return; + } - return row; + void *elm = list->get(list, index); + UiSubListItem item = { NULL, NULL, NULL, NULL, NULL, NULL }; + if(listbox->getvalue) { + listbox->getvalue(list, sublist->userdata, elm, index, &item, listbox->getvaluedata); + } else { + item.label = strdup(elm); + } + + LISTBOX_ROW_REMOVE_CHILD(row); + + listbox_fill_row(listbox, GTK_WIDGET(row), sublist, &item, index); + + // cleanup + free(item.label); + free(item.icon); + free(item.button_label); + free(item.button_icon); + free(item.badge); } void ui_listbox_update_sublist(UiListBox *listbox, UiListBoxSubList *sublist, size_t listbox_insert_index) { @@ -2058,7 +2422,8 @@ } // create listbox item - GtkWidget *row = create_listbox_row(listbox, sublist, &item, (int)index); + GtkWidget *row = gtk_list_box_row_new(); + listbox_fill_row(listbox, row, sublist, &item, (int)index); if(index == 0) { // first row in the sublist, set ui_listbox data to the row // which is then used by the headerfunc @@ -2092,14 +2457,17 @@ void ui_listbox_list_update(UiList *list, int i) { UiListBoxSubList *sublist = list->obj; - ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); - size_t pos = 0; - CxIterator it = cxListIterator(sublist->listbox->sublists); - cx_foreach(UiListBoxSubList *, ls, it) { - ls->startpos = pos; - pos += sublist->numitems; + if(i < 0) { + ui_listbox_update_sublist(sublist->listbox, sublist, sublist->startpos); + size_t pos = 0; + CxIterator it = cxListIterator(sublist->listbox->sublists); + cx_foreach(UiListBoxSubList *, ls, it) { + ls->startpos = pos; + pos += ls->numitems; + } + } else { + update_sublist_item(sublist->listbox, sublist, i); } - } void ui_listbox_row_activate(GtkListBox *self, GtkListBoxRow *row, gpointer user_data) {
--- a/ui/gtk/list.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/list.h Sat Oct 04 14:52:59 2025 +0200 @@ -40,6 +40,13 @@ typedef struct UiColData UiColData; +#if GTK_CHECK_VERSION(4, 10, 0) +typedef struct UiRowItems { + int bound; + GtkListItem *items[]; +} UiRowItems; +#endif + typedef struct UiListView { UiObject *obj; GtkWidget *widget; @@ -47,12 +54,22 @@ UiModel *model; ui_getvaluefunc2 getvalue; void *getvaluedata; + ui_getstylefunc getstyle; + void *getstyledata; char **elements; size_t nelm; + int current_row; + UiTextStyle row_style; + UiBool apply_row_style; #if GTK_CHECK_VERSION(4, 10, 0) + CxMap *bound_rows; GListStore *liststore; GtkSelectionModel *selectionmodel; UiColData *columns; + int numcolumns; + PangoAttrList *current_row_attributes; +#else + int style_offset; #endif ui_callback onactivate; void *onactivatedata; @@ -64,6 +81,8 @@ void *ondragcompletedata; ui_callback ondrop; void *ondropdata; + ui_list_savefunc onsave; + void *onsavedata; UiListSelection selection; } UiListView;
--- a/ui/gtk/menu.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/menu.c Sat Oct 04 14:52:59 2025 +0200 @@ -576,8 +576,12 @@ ls->oldcount = 0; ls->getvalue = il->getvalue; - //UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); - UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); + GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i")); + g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); + snprintf(ls->action, 32, "win.%s", item->id); + + UiVar* var = uic_create_var(ui_global_context(), il->varname, UI_VAR_LIST); + //UiVar* var = uic_create_var(obj->ctx, il->varname, UI_VAR_LIST); ls->var = var; if(var) { UiList *list = var->value; @@ -604,10 +608,6 @@ ls->callback = il->callback; ls->userdata = il->userdata; - GSimpleAction *action = g_simple_action_new(item->id, g_variant_type_new("i")); - g_action_map_add_action(obj->ctx->action_map, G_ACTION(action)); - snprintf(ls->action, 32, "win.%s", item->id); - UiEventData *event = malloc(sizeof(UiEventData)); event->obj = obj; @@ -650,6 +650,10 @@ UiVar *var = event->customdata; UiList *list = var->value; + if(!event->callback) { + return; + } + UiEvent evt; evt.obj = event->obj; evt.window = event->obj->window;
--- a/ui/gtk/range.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/range.c Sat Oct 04 14:52:59 2025 +0200 @@ -126,7 +126,7 @@ #else gtk_adjustment_set_page_size(a, extent); #endif -#if GTK_MAJOR_VERSION * 100 + GTK_MIMOR_VERSION < 318 +#if !GTK_CHECK_VERSION(3, 18, 0) gtk_adjustment_changed(a); #endif range->extent = extent;
--- a/ui/gtk/text.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/text.c Sat Oct 04 14:52:59 2025 +0200 @@ -616,10 +616,11 @@ uitext); if(args->width > 0) { - // TODO: gtk4 -#if GTK_MAJOR_VERSION <= 3 - gtk_entry_set_width_chars(GTK_ENTRY(textfield), args->width); -#endif + // An early implementation used gtk_entry_set_width_chars, + // but that is not available on gtk4 and other toolkits + // also don't have this. Setting the width in pixels can + // be implemented on all platforms + gtk_widget_set_size_request(textfield, args->width, -1); } if(frameless) { // TODO: gtk2legacy workaroud
--- a/ui/gtk/toolkit.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/toolkit.c Sat Oct 04 14:52:59 2025 +0200 @@ -79,7 +79,6 @@ #endif ui_css_init(); - uic_docmgr_init(); uic_menu_init(); uic_toolbar_init(); ui_image_init(); @@ -323,15 +322,15 @@ ui_destroy_boundvar(NULL, var); } +// TODO: move to common void ui_destroy_boundvar(UiContext *ctx, UiVar *var) { + uic_save_var(var); uic_unbind_var(var); + // UI_VAR_SPECIAL: anonymous value variable, that is not registered + // in ctx->vars if(var->type == UI_VAR_SPECIAL) { ui_free(var->from_ctx, var); - } else { - ui_free(var->from_ctx, var); - // TODO: free or unbound - //uic_remove_bound_var(ctx, var); } } @@ -404,6 +403,14 @@ " margin-left: 4px;" " margin-right: 4px;" "}\n" +".ui-nopadding {" +" padding: 0;" +"}\n" +".ui-table-entry {" +" border: none;" +" box-shadow: none;" +" background: transparent;" +"}\n" ; #elif GTK_MAJOR_VERSION == 3 @@ -446,6 +453,9 @@ " margin-left: 4px;" " margin-right: 4px;" "}\n" +".ui-nopadding {" +" padding: 0;" +"}\n" ; #endif
--- a/ui/gtk/toolkit.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/toolkit.h Sat Oct 04 14:52:59 2025 +0200 @@ -74,6 +74,7 @@ #define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon) #define LISTBOX_REMOVE(listbox, row) gtk_list_box_remove(GTK_LIST_BOX(listbox), row) #define LISTBOX_ROW_SET_CHILD(row, child) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), child) +#define LISTBOX_ROW_REMOVE_CHILD(row) gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), NULL) #define PANED_SET_CHILD1(paned, child) gtk_paned_set_start_child(GTK_PANED(paned), child) #define PANED_SET_CHILD2(paned, child) gtk_paned_set_end_child(GTK_PANED(paned), child) #else @@ -96,6 +97,7 @@ #define ICON_IMAGE(icon) gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_BUTTON) #define LISTBOX_REMOVE(listbox, row) gtk_container_remove(GTK_CONTAINER(listbox), row) #define LISTBOX_ROW_SET_CHILD(row, child) gtk_container_add(GTK_CONTAINER(row), child) +#define LISTBOX_ROW_REMOVE_CHILD(row) gtk_container_remove(GTK_CONTAINER(row), gtk_bin_get_child(GTK_BIN(row))) #define PANED_SET_CHILD1(paned, child) gtk_paned_pack1(GTK_PANED(paned), child, TRUE, TRUE) #define PANED_SET_CHILD2(paned, child) gtk_paned_pack2(GTK_PANED(paned), child, TRUE, TRUE) #endif
--- a/ui/gtk/webview.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/webview.c Sat Oct 04 14:52:59 2025 +0200 @@ -103,7 +103,7 @@ } void ui_webview_load_url(UiGeneric *g, const char *url) { - WebViewData data = { .uri = (char*)url, .type = WEBVIEW_CONTENT_URL }; + WebViewData data = { .uri = (char*)url, .type = WEBVIEW_CONTENT_URL, .javascript = TRUE, .zoom = 1 }; g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE); } @@ -129,6 +129,8 @@ data.mimetype = (char*)mimetype; data.encoding = (char*)encoding; data.type = WEBVIEW_CONTENT_CONTENT; + data.javascript = FALSE; + data.zoom = 1; g->set(g, &data, UI_WEBVIEW_OBJECT_TYPE); }
--- a/ui/gtk/window.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/gtk/window.c Sat Oct 04 14:52:59 2025 +0200 @@ -49,6 +49,9 @@ static int window_default_width = 650; static int window_default_height = 550; +static int splitview_window_default_pos = -1; +static UiBool splitview_window_use_prop = TRUE; + static gboolean ui_window_destroy(void *data) { UiObject *obj = data; uic_object_destroy(obj); @@ -77,6 +80,36 @@ } static gboolean ui_window_close_request(UiObject *obj) { + if(obj->widget) { + void *appwindow = g_object_get_data(G_OBJECT(obj->widget), "ui.appwindow"); + if(appwindow) { + int width = 0; + int height = 0; +#if GTK_CHECK_VERSION(4, 10, 0) + graphene_rect_t bounds; + if(gtk_widget_compute_bounds(obj->widget, obj->widget, &bounds)) { + width = bounds.size.width; + height = bounds.size.height; + } +#elif GTK_CHECK_VERSION(4, 0, 0) + GtkAllocation alloc; + gtk_widget_get_allocation(GTK_WIDGET(obj->widget), &alloc); + width = alloc.width; + height = alloc.height; +#else + gtk_window_get_size(GTK_WINDOW(obj->widget), &width, &height); +#endif + if(width > 0 && height > 0) { + char width_str[32]; + char height_str[32]; + snprintf(width_str, 32, "%d", width); + snprintf(height_str, 32, "%d", height); + ui_set_property("ui.window.width", width_str); + ui_set_property("ui.window.height", height_str); + } + } + } + uic_context_prepare_close(obj->ctx); obj->ref--; if(obj->ref > 0) { @@ -101,7 +134,14 @@ } #endif -static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool simple) { +static void save_window_splitview_pos(GtkWidget *widget, void *unused) { + int pos = gtk_paned_get_position(GTK_PANED(widget)); + char buf[32]; + snprintf(buf, 32, "%d", pos); + ui_set_property("ui.window.splitview.pos", buf); +} + +static UiObject* create_window(const char *title, void *window_data, UiBool sidebar, UiBool splitview, UiBool simple) { UiObject *obj = uic_object_new_toplevel(); #ifdef UI_LIBADWAITA @@ -122,19 +162,28 @@ gtk_window_set_title(GTK_WINDOW(obj->widget), title); } - const char *width = ui_get_property("ui.window.width"); - const char *height = ui_get_property("ui.window.height"); - if(width && height) { - gtk_window_set_default_size( + if(!simple) { + g_object_set_data(G_OBJECT(obj->widget), "ui.appwindow", obj); + } + + int window_width = window_default_width; + int window_height = window_default_height; + if(!simple) { + const char *width = ui_get_property("ui.window.width"); + const char *height = ui_get_property("ui.window.height"); + if(width && height) { + int w = atoi(width); + int h = atoi(height); + if(w > 0 && h > 0) { + window_width = w; + window_height = h; + } + } + } + gtk_window_set_default_size( GTK_WINDOW(obj->widget), - atoi(width), - atoi(height)); - } else { - gtk_window_set_default_size( - GTK_WINDOW(obj->widget), - window_default_width + sidebar*250, - window_default_height); - } + window_width, + window_height); obj->destroy = ui_window_widget_destroy; g_signal_connect( @@ -161,50 +210,95 @@ GtkWidget *toolbar_view = adw_toolbar_view_new(); adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(toolbar_view), vbox); - GtkWidget *content_box = ui_gtk_vbox_new(0); - BOX_ADD_EXPAND(GTK_BOX(vbox), content_box); + GtkWidget *headerbar_sidebar = NULL; + GtkWidget *headerbar_main = adw_header_bar_new(); + GtkWidget *headerbar_right = NULL; - GtkWidget *sidebar_headerbar = NULL; + GtkWidget *content = toolbar_view; + if(splitview) { + content = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL); + g_signal_connect( + content, + "destroy", + G_CALLBACK(save_window_splitview_pos), + NULL); + + const char *splitview_pos_str = ui_get_property("ui.window.splitview.pos"); + int pos = splitview_window_default_pos; + if(pos < 0) { + pos = window_width / 2; + } + if(splitview_pos_str && splitview_window_use_prop) { + int splitview_pos = atoi(splitview_pos_str); + if(splitview_pos > 0) { + pos = splitview_pos; + } + } + gtk_paned_set_position(GTK_PANED(content), pos); + + GtkWidget *right_panel = adw_toolbar_view_new(); + GtkWidget *right_vbox = ui_gtk_vbox_new(0); + adw_toolbar_view_set_content(ADW_TOOLBAR_VIEW(right_panel), right_vbox); + + headerbar_right = adw_header_bar_new(); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_right), FALSE); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(right_panel), headerbar_right); + + adw_header_bar_set_show_end_title_buttons(ADW_HEADER_BAR(headerbar_main), FALSE); + + gtk_paned_set_start_child(GTK_PANED(content), toolbar_view); + gtk_paned_set_end_child(GTK_PANED(content), right_panel); + + g_object_set_data(G_OBJECT(obj->widget), "ui_window_splitview", content); + g_object_set_data(G_OBJECT(obj->widget), "ui_left_panel", vbox); + g_object_set_data(G_OBJECT(obj->widget), "ui_right_panel", right_vbox); + } + + GtkWidget *content_box = vbox; + if(sidebar) { GtkWidget *splitview = adw_overlay_split_view_new(); adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), splitview); GtkWidget *sidebar_toolbar_view = adw_toolbar_view_new(); adw_overlay_split_view_set_sidebar(ADW_OVERLAY_SPLIT_VIEW(splitview), sidebar_toolbar_view); - sidebar_headerbar = adw_header_bar_new(); - adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), sidebar_headerbar); + headerbar_sidebar = adw_header_bar_new(); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(sidebar_toolbar_view), headerbar_sidebar); - adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), toolbar_view); + adw_overlay_split_view_set_content(ADW_OVERLAY_SPLIT_VIEW(splitview), content); g_object_set_data(G_OBJECT(obj->widget), "ui_sidebar", sidebar_toolbar_view); } else { - adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), toolbar_view); + adw_application_window_set_content(ADW_APPLICATION_WINDOW(obj->widget), content); } - GtkWidget *headerbar = adw_header_bar_new(); - const char *show_title = ui_get_property("ui.gtk.window.showtitle"); if(show_title) { if(!strcmp(show_title, "main") && sidebar) { - adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); } else if(!strcmp(show_title, "sidebar")) { - adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), TRUE); } else if(!strcmp(show_title, "false")) { - adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE); - adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE); } else { fprintf(stderr, "Unknown value '%s' for property ui.gtk.window.showtitle\n", show_title); - adw_header_bar_set_show_title(ADW_HEADER_BAR(sidebar_headerbar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), FALSE); } } else { - adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar), FALSE); + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_main), FALSE); + if(sidebar) { + adw_header_bar_set_show_title(ADW_HEADER_BAR(headerbar_sidebar), TRUE); + } } - adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar); - g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar); + adw_toolbar_view_add_top_bar(ADW_TOOLBAR_VIEW(toolbar_view), headerbar_main); + g_object_set_data(G_OBJECT(obj->widget), "ui_headerbar", headerbar_main); if(!simple) { - ui_fill_headerbar(obj, headerbar); + ui_fill_headerbar(obj, headerbar_sidebar, headerbar_main, headerbar_right); } #elif GTK_MAJOR_VERSION >= 4 GtkWidget *content_box = ui_gtk_vbox_new(0); @@ -286,15 +380,19 @@ UiObject* ui_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, FALSE); + return create_window(title, window_data, FALSE, FALSE, FALSE); } UiObject *ui_sidebar_window(const char *title, void *window_data) { - return create_window(title, window_data, TRUE, FALSE); + return create_window(title, window_data, TRUE, FALSE, FALSE); +} + +UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar) { + return create_window(title, NULL, sidebar, TRUE, FALSE); } UiObject* ui_simple_window(const char *title, void *window_data) { - return create_window(title, window_data, FALSE, TRUE); + return create_window(title, window_data, FALSE, FALSE, TRUE); } void ui_window_size(UiObject *obj, int width, int height) { @@ -304,6 +402,38 @@ height); } +void ui_window_default_size(int width, int height) { + window_default_width = width; + window_default_height = height; +} + +void ui_splitview_window_set_pos(UiObject *obj, int pos) { + GtkWidget *splitview = g_object_get_data(G_OBJECT(obj->widget), "ui_window_splitview"); + if(splitview) { + gtk_paned_set_position(GTK_PANED(splitview), pos); + } else { + fprintf(stderr, "Error: window has no splitview\n"); + } +} + +int ui_splitview_window_get_pos(UiObject *obj) { + GtkWidget *splitview = g_object_get_data(G_OBJECT(obj->widget), "ui_window_splitview"); + if(splitview) { + return gtk_paned_get_position(GTK_PANED(splitview)); + } else { + fprintf(stderr, "Error: window has no splitview\n"); + } + return 0; +} + +void ui_splitview_window_set_default_pos(int pos) { + splitview_window_default_pos = pos; +} + +void ui_splitview_window_use_property(UiBool enable) { + splitview_window_use_prop = enable; +} + #ifdef UI_LIBADWAITA static void dialog_response(AdwAlertDialog *self, gchar *response, UiEventData *data) { @@ -693,18 +823,6 @@ G_CALLBACK(ui_destroy_userdata), event); - - UiEvent evt; - evt.obj = obj; - evt.document = evt.obj->ctx->document; - evt.window = evt.obj->window; - evt.intval = 0; - - UiFileList flist; - flist.files = NULL; - flist.nfiles = 0; - evt.eventdata = &flist; - gtk_widget_show(dialog); } #endif
--- a/ui/motif/Grid.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/motif/Grid.c Sat Oct 04 14:52:59 2025 +0200 @@ -299,31 +299,32 @@ WidgetClass gridClass = (WidgetClass)&gridClassRec; -void grid_class_initialize(Widget request, Widget new, ArgList args, Cardinal *num_args) { +void grid_class_initialize(void) { } void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args) { - MyWidget mn = (MyWidget)new; + Grid mn = (Grid)new; mn->mywidget.max_col = 0; mn->mywidget.max_row = 0; } -void grid_realize(MyWidget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) { - XtMakeResizeRequest((Widget)w, 400, 400, NULL, NULL); +void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes) { + Grid grid = (Grid)w; + XtMakeResizeRequest(w, 400, 400, NULL, NULL); (coreClassRec.core_class.realize)((Widget)w, valueMask, attributes); - grid_place_children(w); + grid_place_children(grid); } -void grid_destroy(MyWidget widget) { +void grid_destroy(Grid widget) { } -void grid_resize(MyWidget widget) { +void grid_resize(Grid widget) { grid_place_children(widget); } -void grid_expose(MyWidget widget, XEvent *event, Region region) { +void grid_expose(Grid widget, XEvent *event, Region region) { } @@ -336,11 +337,11 @@ } -void grid_getfocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) { +void grid_getfocus(Widget myw, XEvent *event, String *params, Cardinal *nparam) { } -void grid_loosefocus(MyWidget myw, XEvent *event, String *params, Cardinal *nparam) { +void grid_loosefocus(Widget myw, XEvent *event, String *params, Cardinal *nparam) { } @@ -358,7 +359,7 @@ widget->core.height = request->height; constraints->grid.pref_height = request->height; } - grid_place_children((MyWidget)XtParent(widget)); + grid_place_children((Grid)XtParent(widget)); return XtGeometryYes; } @@ -368,7 +369,7 @@ Boolean ConstraintSetValues(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args) { GridConstraintRec *constraints = neww->core.constraints; - MyWidget grid = (MyWidget)XtParent(neww); + Grid grid = (Grid)XtParent(neww); if(constraints->grid.x > grid->mywidget.max_col) { grid->mywidget.max_col = constraints->grid.x; } @@ -387,7 +388,7 @@ { GridConstraintRec *constraints = neww->core.constraints; - MyWidget grid = (MyWidget)XtParent(neww); + Grid grid = (Grid)XtParent(neww); if(constraints->grid.x > grid->mywidget.max_col) { grid->mywidget.max_col = constraints->grid.x; } @@ -398,7 +399,7 @@ constraints->grid.pref_height = neww->core.height; } -void grid_place_children(MyWidget w) { +void grid_place_children(Grid w) { int ncols = w->mywidget.max_col+1; int nrows = w->mywidget.max_row+1; GridDef *cols = calloc(ncols, sizeof(GridDef));
--- a/ui/motif/Grid.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/motif/Grid.h Sat Oct 04 14:52:59 2025 +0200 @@ -125,23 +125,23 @@ GridContraintPart grid; } GridConstraintRec; -typedef GridRec* MyWidget; +typedef GridRec* Grid; extern WidgetClass gridClass; -void grid_class_initialize(); -void grid_initialize(); -void grid_realize(); -void grid_destroy(); -void grid_resize(); -void grid_expose(); -Boolean grid_set_values(); +void grid_class_initialize(void); +void grid_initialize(Widget request, Widget new, ArgList args, Cardinal num_args); +void grid_realize(Widget w,XtValueMask *valueMask,XSetWindowAttributes *attributes); +void grid_destroy(Grid widget); +void grid_resize(Grid widget); +void grid_expose(Grid widget, XEvent *event, Region region); +Boolean grid_set_values(Widget old, Widget request, Widget neww, ArgList args, Cardinal *num_args); Boolean grid_acceptfocus(Widget , Time*); -void grid_place_children(MyWidget w); +void grid_place_children(Grid w); -void grid_getfocus(); -void grid_loosefocus(); +void grid_getfocus(Widget myw, XEvent *event, String *params, Cardinal *nparam); +void grid_loosefocus(Widget myw, XEvent *event, String *params, Cardinal *nparam); void grid_constraint_init( Widget request,
--- a/ui/motif/toolkit.c Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/motif/toolkit.c Sat Oct 04 14:52:59 2025 +0200 @@ -100,7 +100,6 @@ display = XtOpenDisplay(app, NULL, appname, appname, NULL, 0, &argc, argv); - uic_docmgr_init(); uic_menu_init(); uic_toolbar_init(); uic_load_app_properties();
--- a/ui/qt/entry.cpp Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/qt/entry.cpp Sat Oct 04 14:52:59 2025 +0200 @@ -36,14 +36,19 @@ -UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs *args) { +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args) { UiContainerPrivate *ctn = ui_obj_container(obj); UI_APPLY_LAYOUT(ctn->layout, args); + double min = args->min; + double max = args->max != 0 ? args->max : 100000; + bool use_double = false; UiVar *var = NULL; + UiVarType vartype = UI_VAR_SPECIAL; if(args->varname) { var = uic_get_var(obj->ctx, args->varname); + vartype = var->type; if(var->type == UI_VAR_DOUBLE) { use_double = true; } else if(var->type == UI_VAR_RANGE) { @@ -57,11 +62,14 @@ if(!var) { if(args->intvalue) { var = uic_widget_var(obj->ctx, obj->ctx, args->intvalue, NULL, UI_VAR_INTEGER); + vartype = UI_VAR_INTEGER; } else if(args->doublevalue) { var = uic_widget_var(obj->ctx, obj->ctx, args->doublevalue, NULL, UI_VAR_DOUBLE); + vartype = UI_VAR_DOUBLE; use_double = true; } else if(args->rangevalue) { var = uic_widget_var(obj->ctx, obj->ctx, args->rangevalue, NULL, UI_VAR_RANGE); + vartype = UI_VAR_RANGE; use_double = true; } else { if(args->digits > 0) { @@ -70,6 +78,12 @@ } } + if(vartype == UI_VAR_RANGE) { + UiRange *r = (UiRange*)var->value; + min = r->min; + max = r->max; + } + QAbstractSpinBox *widget = nullptr; if(use_double) { QDoubleSpinBox *spinbox = new QDoubleSpinBox(); @@ -77,17 +91,21 @@ if(args->step != 0) { spinbox->setSingleStep(args->step); } + spinbox->setMinimum(min); + spinbox->setMaximum(max); widget = spinbox; } else { QSpinBox *spinbox = new QSpinBox(); if(args->step != 0) { spinbox->setSingleStep(args->step); } + spinbox->setMinimum((int)min); + spinbox->setMaximum((int)max); widget = spinbox; } if(var) { - if(var->type == UI_VAR_INTEGER) { + if(vartype == UI_VAR_INTEGER) { UiInteger *value = (UiInteger*)var->value; value->obj = widget; if(value->value != 0) { @@ -96,7 +114,7 @@ } value->get = ui_spinbox_int_get; value->set = ui_spinbox_int_set; - } else if(var->type == UI_VAR_DOUBLE) { + } else if(vartype == UI_VAR_DOUBLE) { UiDouble *value = (UiDouble*)var->value; value->obj = widget; if(value->value != 0) { @@ -105,7 +123,7 @@ } value->get = ui_spinbox_double_get; value->set = ui_spinbox_double_set; - } else if(var->type == UI_VAR_RANGE) { + } else if(vartype == UI_VAR_RANGE) { UiRange *value = (UiRange*)var->value; value->obj = widget; QDoubleSpinBox *spinbox = (QDoubleSpinBox*)widget;
--- a/ui/qt/toolkit.cpp Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/qt/toolkit.cpp Sat Oct 04 14:52:59 2025 +0200 @@ -62,7 +62,6 @@ app_argv = argv; application = new QApplication(app_argc, app_argv); - uic_docmgr_init(); uic_menu_init(); uic_toolbar_init();
--- a/ui/qt/widget.cpp Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/qt/widget.cpp Sat Oct 04 14:52:59 2025 +0200 @@ -52,6 +52,14 @@ return separator; } +void ui_set_enabled(UIWIDGET widget, int enabled) { + widget->setEnabled(enabled); +} + +void ui_set_visible(UIWIDGET widget, int visible) { + widget->setVisible(visible); +} + void ui_widget_set_size(UIWIDGET w, int width, int height) { w->resize(width >= 0 ? width : w->width(), height >= 0 ? height : w->height()); }
--- a/ui/ui/container.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/container.h Sat Oct 04 14:52:59 2025 +0200 @@ -128,9 +128,6 @@ int spacing; int columnspacing; int rowspacing; - - const char* label; - UiBool isexpanded; } UiTabViewArgs; typedef struct UiHeaderbarArgs { @@ -177,6 +174,7 @@ int rowspacing; int initial_position; + const char *position_property; UiInteger *value; const char* varname; int max_panes; @@ -246,6 +244,8 @@ #define ui_tabview(obj, ...) for(ui_tabview_create(obj, &(UiTabViewArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_headerbar(obj, ...) for(ui_headerbar_create(obj, &(UiHeaderbarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_sidebar(obj, ...) for(ui_sidebar_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_left_panel(obj, ...) for(ui_left_panel_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_right_panel(ob, ...) for(ui_right_panel_create(obj, &(UiSidebarArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_vbox0(obj) for(ui_vbox_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hbox0(obj) for(ui_hbox_create(obj, &(UiContainerArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) @@ -256,6 +256,9 @@ #define ui_tabview0(obj) for(ui_tabview_create(obj, &(UiTabViewArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_headerbar0(obj) for(ui_headerbar_create(obj, &(UiHeaderbarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_sidebar0(obj) for(ui_sidebar_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_left_panel0(obj) for(ui_left_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) +#define ui_right_panel0(obj) for(ui_right_panel_create(obj, &(UiSidebarArgs){ 0 });ui_container_finish(obj);ui_container_begin_close(obj)) + #define ui_vbox_w(obj, w, ...) for(w = ui_vbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) #define ui_hbox_w(obj, w, ...) for(w = ui_hbox_create(obj, &(UiContainerArgs){ __VA_ARGS__ });ui_container_finish(obj);ui_container_begin_close(obj)) @@ -301,6 +304,8 @@ UIEXPORT void ui_headerbar_end_create(UiObject *obj); UIEXPORT UIWIDGET ui_sidebar_create(UiObject *obj, UiSidebarArgs *args); +UIEXPORT UIWIDGET ui_left_panel_create(UiObject *obj, UiSidebarArgs *args); +UIEXPORT UIWIDGET ui_right_panel_create(UiObject *obj, UiSidebarArgs *args); UIEXPORT UIWIDGET ui_itemlist_create(UiObject *obj, UiItemListContainerArgs *args);
--- a/ui/ui/entry.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/entry.h Sat Oct 04 14:52:59 2025 +0200 @@ -36,7 +36,7 @@ #endif -typedef struct UiSpinnerArgs { +typedef struct UiSpinBoxArgs { UiBool fill; UiBool hexpand; UiBool vexpand; @@ -45,11 +45,14 @@ UiBool override_defaults; int colspan; int rowspan; + int width; const char *name; const char *style_class; double step; int digits; + double min; + double max; UiInteger *intvalue; UiDouble* doublevalue; UiRange *rangevalue; @@ -58,13 +61,13 @@ void* onchangedata; const int *groups; -} UiSpinnerArgs; +} UiSpinBoxArgs; -UIWIDGET ui_spinner_create(UiObject *obj, UiSpinnerArgs *args); +UIWIDGET ui_spinbox_create(UiObject *obj, UiSpinBoxArgs *args); -#define ui_spinner(obj, ...) ui_spinner_create(obj, &(UiSpinnerArgs){ __VA_ARGS__ } ) +#define ui_spinbox(obj, ...) ui_spinbox_create(obj, &(UiSpinBoxArgs){ __VA_ARGS__ } ) void ui_spinner_setrange(UIWIDGET spinner, double min, double max); void ui_spinner_setdigits(UIWIDGET spinner, int digits);
--- a/ui/ui/properties.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/properties.h Sat Oct 04 14:52:59 2025 +0200 @@ -41,14 +41,14 @@ int ui_properties_store(void); -void ui_locales_dir(char *path); -void ui_pixmaps_dir(char *path); +void ui_locales_dir(const char *path); +void ui_pixmaps_dir(const char *path); -void ui_load_lang(char *locale); +void ui_load_lang(const char *locale); void ui_load_lang_def(char *locale, char *default_locale); -char* uistr(char *name); -char* uistr_n(char *name); +char* uistr(const char *name); +char* uistr_n(const char *name); #ifdef __cplusplus }
--- a/ui/ui/toolbar.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/toolbar.h Sat Oct 04 14:52:59 2025 +0200 @@ -37,38 +37,43 @@ #endif typedef struct UiToolbarItemArgs { - const char* label; - const char* stockid; - const char* icon; + const char* label; + const char* stockid; + const char* icon; - ui_callback onclick; - void* onclickdata; - - const int *groups; + ui_callback onclick; + void* onclickdata; + + const int *groups; } UiToolbarItemArgs; typedef struct UiToolbarToggleItemArgs { - const char* label; - const char* stockid; - const char* icon; + const char* label; + const char* stockid; + const char* icon; - const char* varname; - ui_callback onchange; - void* onchangedata; - - const int *groups; + const char* varname; + ui_callback onchange; + void* onchangedata; + + const int *groups; } UiToolbarToggleItemArgs; typedef struct UiToolbarMenuArgs { - const char* label; - const char* stockid; - const char* icon; + const char* label; + const char* stockid; + const char* icon; } UiToolbarMenuArgs; enum UiToolbarPos { - UI_TOOLBAR_LEFT = 0, - UI_TOOLBAR_CENTER, - UI_TOOLBAR_RIGHT + UI_TOOLBAR_LEFT = 0, + UI_TOOLBAR_CENTER, + UI_TOOLBAR_RIGHT, + UI_TOOLBAR_SIDEBAR_LEFT, + UI_TOOLBAR_SIDEBAR_RIGHT, + UI_TOOLBAR_RIGHTPANEL_LEFT, + UI_TOOLBAR_RIGHTPANEL_CENTER, + UI_TOOLBAR_RIGHTPANEL_RIGHT }; #define ui_toolbar_item(name, ...) ui_toolbar_item_create(name, &(UiToolbarItemArgs){ __VA_ARGS__ } )
--- a/ui/ui/toolkit.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/toolkit.h Sat Oct 04 14:52:59 2025 +0200 @@ -200,6 +200,9 @@ typedef struct UiListSelection UiListSelection; +typedef struct UiTextStyle UiTextStyle; +typedef struct UiColor UiColor; + /* begin opaque types */ typedef struct UiContext UiContext; typedef struct UiContainer UiContainer; @@ -251,6 +254,7 @@ typedef void*(*ui_getvaluefunc)(void *elm, int col); typedef void*(*ui_getvaluefunc2)(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult); +typedef UiBool(*ui_getstylefunc)(UiList *list, void *elm, int row, int col, void *userdata, UiTextStyle *style); typedef int(*ui_threadfunc)(void*); @@ -502,6 +506,23 @@ UI_EVENT_DATA_FILE_LIST }; +#define UI_COLOR(r, g, b) (UiColor){r, g, b} +struct UiColor { + uint8_t red; + uint8_t green; + uint8_t blue; +}; + +#define UI_TEXT_STYLE_BOLD 1 +#define UI_TEXT_STYLE_ITALIC 2 +#define UI_TEXT_STYLE_UNDERLINE 4 + +struct UiTextStyle { + uint32_t text_style; + UiColor fg; + UiBool fg_set; +}; + UIEXPORT void ui_init(const char *appname, int argc, char **argv); UIEXPORT const char* ui_appname(); @@ -547,6 +568,8 @@ UIEXPORT void ui_detach_document(UiContext *ctx, void *document); UIEXPORT void ui_widget_set_groups(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, ...); +UIEXPORT void ui_widget_set_groups2(UiContext *ctx, UIWIDGET widget, ui_enablefunc enable, int *groups, int ngroups); +UIEXPORT void ui_widget_set_visibility_states(UiContext *ctx, UIWIDGET widget, int *states, int nstates); UIEXPORT void ui_set_group(UiContext *ctx, int group); UIEXPORT void ui_unset_group(UiContext *ctx, int group); @@ -590,6 +613,13 @@ UIEXPORT char* ui_string_get(UiString *s); UIEXPORT void ui_text_set(UiText *s, const char* value); UIEXPORT char* ui_text_get(UiText *s); +UIEXPORT void ui_range_set(UiRange *r, double value); +UIEXPORT void ui_range_set_range(UiRange *r, double min, double max); +UIEXPORT void ui_range_set_extent(UiRange *r, double extent); +UIEXPORT double ui_range_get(UiRange *r); +UIEXPORT double ui_range_get_min(UiRange *r); +UIEXPORT double ui_range_get_max(UiRange *r); +UIEXPORT double ui_range_get_extent(UiRange *r); UIEXPORT void ui_generic_set_image(UiGeneric *g, void *img); UIEXPORT void* ui_generic_get_image(UiGeneric *g); @@ -636,11 +666,6 @@ UIEXPORT void ui_add_image(char *imgname, char *filename); // TODO: remove? -// general widget functions -UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled); -UIEXPORT void ui_set_show_all(UIWIDGET widget, int value); -UIEXPORT void ui_set_visible(UIWIDGET widget, int visible); - UIEXPORT void ui_listselection_free(UiListSelection selection); @@ -651,7 +676,7 @@ UIEXPORT char* ui_getappdir(void); -UIEXPORT char* ui_configfile(char *name); +UIEXPORT char* ui_configfile(const char *name); UIEXPORT UiCondVar* ui_condvar_create(void); UIEXPORT void ui_condvar_wait(UiCondVar *var);
--- a/ui/ui/tree.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/tree.h Sat Oct 04 14:52:59 2025 +0200 @@ -51,9 +51,20 @@ UI_INTEGER, UI_ICON, UI_ICON_TEXT, - UI_ICON_TEXT_FREE + UI_ICON_TEXT_FREE, + UI_STRING_EDITABLE } UiModelType; +typedef struct UiCellValue { + union { + const char *string; + int64_t i; + }; + UiModelType type; +} UiCellValue; + +typedef UiBool (*ui_list_savefunc)(UiList *list, int row, int col, UiCellValue *value, void *userdata); + struct UiModel { /* * number of columns @@ -61,6 +72,11 @@ int columns; /* + * current allocation size (internal) + */ + int alloc; + + /* * array of column types * array length is the number of columns */ @@ -115,6 +131,8 @@ ui_getvaluefunc getvalue; ui_getvaluefunc2 getvalue2; void *getvalue2data; + ui_getstylefunc getstyle; + void *getstyledata; ui_callback onactivate; void* onactivatedata; ui_callback onselection; @@ -127,6 +145,8 @@ void* ondropdata; UiBool multiselection; UiMenuBuilder *contextmenu; + ui_list_savefunc onsave; + void *onsavedata; const int *groups; }; @@ -247,6 +267,8 @@ * UiModel *model = ui_model(ctx, UI_STRING, "Column 1", UI_STRING, "Column 2", -1); */ UIEXPORT UiModel* ui_model(UiContext *ctx, ...); +UIEXPORT UiModel* ui_model_new(UiContext *ctx); +UIEXPORT void ui_model_add_column(UiContext *ctx, UiModel *model, UiModelType type, const char *title, int width); UIEXPORT UiModel* ui_model_copy(UiContext *ctx, UiModel* model); UIEXPORT void ui_model_free(UiContext *ctx, UiModel *mi);
--- a/ui/ui/widget.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/widget.h Sat Oct 04 14:52:59 2025 +0200 @@ -69,6 +69,9 @@ #define ui_separator(obj, ...) ui_separator_create(obj, &(UiWidgetArgs){ __VA_ARGS__ } ) +UIEXPORT void ui_set_enabled(UIWIDGET widget, int enabled); +UIEXPORT void ui_set_visible(UIWIDGET widget, int visible); + UIEXPORT void ui_widget_set_size(UIWIDGET w, int width, int height); UIEXPORT void ui_widget_redraw(UIWIDGET w);
--- a/ui/ui/window.h Sun Aug 24 15:24:16 2025 +0200 +++ b/ui/ui/window.h Sat Oct 04 14:52:59 2025 +0200 @@ -74,6 +74,7 @@ UIEXPORT UiObject *ui_window(const char *title, void *window_data); UIEXPORT UiObject *ui_sidebar_window(const char *title, void *window_data); +UIEXPORT UiObject *ui_splitview_window(const char *title, UiBool sidebar); UIEXPORT UiObject *ui_simple_window(const char *title, void *window_data); UIEXPORT UiObject *ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs *args); @@ -81,6 +82,12 @@ #define ui_dialog_window0(parent) ui_dialog_window_create(parent, &(UiDialogWindowArgs){ 0 }); UIEXPORT void ui_window_size(UiObject *obj, int width, int height); +UIEXPORT void ui_window_default_size(int width, int height); + +UIEXPORT void ui_splitview_window_set_pos(UiObject *obj, int pos); +UIEXPORT int ui_splitview_window_get_pos(UiObject *obj); +UIEXPORT void ui_splitview_window_set_default_pos(int pos); +UIEXPORT void ui_splitview_window_use_property(UiBool enable); #define ui_dialog(parent, ...) ui_dialog_create(parent, &(UiDialogArgs){ __VA_ARGS__ } )