From 5cbb735647c0baaf10688f0865afe79ed11be378 Mon Sep 17 00:00:00 2001 From: Jim Powers Date: Mon, 25 Sep 2023 19:39:03 -0400 Subject: [PATCH] Example of TableView (#217) - Shows how to create a TableView - Dynamically updates data in the table --- macos/_examples/tableview/main.go | 254 ++++++++++++++++++ .../table_view_data_source_delegate.go | 170 ++++++++++++ macos/appkit/enumtypes.gen.go | 2 +- 3 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 macos/_examples/tableview/main.go create mode 100644 macos/_examples/tableview/table_view_data_source_delegate.go diff --git a/macos/_examples/tableview/main.go b/macos/_examples/tableview/main.go new file mode 100644 index 00000000..bbfa5879 --- /dev/null +++ b/macos/_examples/tableview/main.go @@ -0,0 +1,254 @@ +package main + +import ( + "context" + "fmt" + "github.com/progrium/macdriver/dispatch" + "github.com/progrium/macdriver/macos/appkit" + "github.com/progrium/macdriver/macos/foundation" + "github.com/progrium/macdriver/objc" + "runtime" + "sync" + "time" +) + +const ( + RowHeight = 20 +) + +type App struct { + app appkit.Application + windowClosed bool + doStop func() +} + +func (a *App) Stop() { + a.doStop() +} + +func newApp(app appkit.Application) *App { + return &App{ + app: app, + } +} + +func main() { + runtime.LockOSThread() + + app := appkit.Application_SharedApplication() + tvApp := newApp(app) + delegate := &appkit.ApplicationDelegate{} + delegate.SetApplicationDidFinishLaunching(func(foundation.Notification) { + tvApp.mainWindow() + }) + delegate.SetApplicationWillFinishLaunching(func(foundation.Notification) { + tvApp.setMainMenu() + }) + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + return true + }) + delegate.SetApplicationWillTerminate(func(foundation.Notification) { + tvApp.Stop() + }) + + app.SetDelegate(delegate) + app.Run() +} + +type TableViewDataSource struct { + values chan [][2]objc.Object + data [][2]objc.Object + delegate *TableViewDataSourceDelegate + wg *sync.WaitGroup + maxRows int + overflowValue int +} + +func (t *TableViewDataSource) Wait() { + t.wg.Wait() +} + +func (t *TableViewDataSource) Start(ctx context.Context, tableView appkit.TableView) { + t.wg.Add(1) + go valueGenerator(ctx, t.wg, t.values, tableView, t.maxRows, t.overflowValue) +} + +func valueGenerator(ctx context.Context, wg *sync.WaitGroup, values chan<- [][2]objc.Object, tableView appkit.TableView, maxRows int, overflowValue int) { + defer wg.Done() + currentValues := make([][2]int, maxRows) + sendToUI := func() { + snapshot := make([][2]objc.Object, len(currentValues)) + for i, v := range currentValues { + snapshot[i] = [2]objc.Object{foundation.String_StringWithString(fmt.Sprintf("%d", v[0])).Object, foundation.String_StringWithString(fmt.Sprintf("%d", v[1])).Object} + } + select { + case <-ctx.Done(): + return + case values <- snapshot: + dispatch.MainQueue().DispatchAsync(func() { + tableView.SetNeedsDisplay(true) + }) + } + } + counter := 0 + row := 0 + for { + row = counter / 2 + if row >= maxRows { + row = 0 + break + } + currentValues[row][counter%2] = counter % overflowValue + counter++ + } + sendToUI() + + for { + select { + case <-ctx.Done(): + return + default: + time.Sleep(10 * time.Millisecond) + row = (counter / 2) % maxRows + currentValues[row][counter%2] = counter % overflowValue + counter++ + sendToUI() + } + } + +} + +func (t *TableViewDataSource) getLatest(ctx context.Context) { + switch t.data { + case nil: + select { + case t.data = <-t.values: + case <-ctx.Done(): + t.data = nil + return + } + default: + select { + case t.data = <-t.values: + case <-ctx.Done(): + t.data = nil + return + default: + } + } +} + +func NewTableViewDataSource(ctx context.Context, maxRows int, overflowValue int) *TableViewDataSource { + model := &TableViewDataSource{ + maxRows: maxRows, + overflowValue: overflowValue, + values: make(chan [][2]objc.Object, 1), + data: nil, + delegate: &TableViewDataSourceDelegate{}, + wg: &sync.WaitGroup{}, + } + model.delegate.SetTableViewObjectValueForTableColumnRow(func(tableView appkit.TableView, tableColumn appkit.TableColumn, row int) objc.Object { + model.getLatest(ctx) + if model.data == nil { + return objc.ObjectFrom(nil) + } + switch tableColumn.Identifier() { + case "Column1": + return model.data[row][0] + case "Column2": + return model.data[row][1] + } + panic("unknown column") + }) + model.delegate.SetNumberOfRowsInTableView(func(tableView appkit.TableView) int { + model.getLatest(ctx) + return len(model.data) + }) + return model +} + +func (a *App) mainWindow() { + w := appkit.NewWindowWithSize(300, 400) + objc.Retain(&w) + a.windowClosed = false + + wd := &appkit.WindowDelegate{} + wd.SetWindowWillClose(func(notification foundation.Notification) { + a.windowClosed = true + }) + w.SetDelegate(wd) + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + dataSource := NewTableViewDataSource(ctx, 40, 13) + a.doStop = func() { + cancel() + dataSource.Wait() + if !a.windowClosed { + w.Close() + } + } + + w.SetTitle("Test table view") + + tableView := appkit.NewTableView() + go dataSource.Start(ctx, tableView) + tableView.SetRowHeight(RowHeight) + tableView.SetTranslatesAutoresizingMaskIntoConstraints(false) + tableView.SetHeaderView(appkit.NewTableHeaderViewWithFrame(rectOf(0, 0, 0, RowHeight))) + tableView.SetGridStyleMask(appkit.TableViewSolidVerticalGridLineMask | appkit.TableViewSolidHorizontalGridLineMask) + tableView.SetStyle(appkit.TableViewStylePlain) + tableView.SetRowSizeStyle(appkit.TableViewRowSizeStyleDefault) + tableView.SetColumnAutoresizingStyle(appkit.TableViewUniformColumnAutoresizingStyle) + tableView.SetUsesAlternatingRowBackgroundColors(true) + tableView.SetStyle(appkit.TableViewStyleFullWidth) + tableView.SetTranslatesAutoresizingMaskIntoConstraints(false) + tableColumn1 := appkit.NewTableColumn().InitWithIdentifier("Column1") + tableColumn1.SetTitle("Test 1") + tableColumn1.SetWidth(100) + tableView.AddTableColumn(tableColumn1) + tableColumn2 := appkit.NewTableColumn().InitWithIdentifier("Column2") + tableColumn2.SetTitle("Test 2") + tableColumn2.SetWidth(100) + tableView.AddTableColumn(tableColumn2) + tableView.SetDataSource(dataSource.delegate) + tableView.SetAllowsColumnSelection(true) + tableView.SetAutoresizingMask(appkit.ViewWidthSizable | appkit.ViewHeightSizable) + + tsv := appkit.NewScrollView() + tsv.SetFrameSize(foundation.Size{Width: w.ContentView().Frame().Size.Width, Height: w.ContentView().Frame().Size.Height}) + tsv.SetFrameOrigin(foundation.Point{X: w.ContentView().Frame().Origin.X, Y: w.ContentView().Frame().Origin.Y}) + tsv.SetTranslatesAutoresizingMaskIntoConstraints(false) + tsv.SetDocumentView(tableView) + tsv.SetHasHorizontalScroller(true) + tsv.SetHasVerticalScroller(true) + tsv.SetAutohidesScrollers(true) + tsv.SetTranslatesAutoresizingMaskIntoConstraints(true) + tsv.SetAutoresizingMask(appkit.ViewWidthSizable | appkit.ViewHeightSizable) + w.ContentView().AddSubview(tsv) + w.ContentView().LeadingAnchor().ConstraintEqualToAnchorConstant(tsv.LeadingAnchor(), -10).SetActive(true) + w.ContentView().TopAnchor().ConstraintEqualToAnchorConstant(tsv.TopAnchor(), -10).SetActive(true) + w.ContentView().TrailingAnchor().ConstraintEqualToAnchorConstant(tsv.TrailingAnchor(), 10).SetActive(true) + w.ContentView().BottomAnchor().ConstraintEqualToAnchorConstant(tsv.BottomAnchor(), 10).SetActive(true) + + w.Center() + w.MakeKeyAndOrderFront(nil) + + a.app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + a.app.ActivateIgnoringOtherApps(true) +} + +func (a *App) setMainMenu() { + menuBar := appkit.NewMenuWithTitle("Table View") + a.app.SetMainMenu(menuBar) + + appMenuItem := appkit.NewMenuItemWithSelector("Table View", "", objc.Selector{}) + appMenu := appkit.NewMenuWithTitle("Table View") + appMenu.AddItem(appkit.NewMenuItemWithAction("Quit", "q", func(sender objc.Object) { a.app.Terminate(nil) })) + appMenuItem.SetSubmenu(appMenu) + menuBar.AddItem(appMenuItem) +} + +func rectOf(x, y, width, height float64) foundation.Rect { + return foundation.Rect{Origin: foundation.Point{X: x, Y: y}, Size: foundation.Size{Width: width, Height: height}} +} diff --git a/macos/_examples/tableview/table_view_data_source_delegate.go b/macos/_examples/tableview/table_view_data_source_delegate.go new file mode 100644 index 00000000..f0162ac2 --- /dev/null +++ b/macos/_examples/tableview/table_view_data_source_delegate.go @@ -0,0 +1,170 @@ +package main + +import ( + "github.com/progrium/macdriver/macos/appkit" + "github.com/progrium/macdriver/macos/foundation" + "github.com/progrium/macdriver/objc" +) + +type TableViewDataSourceDelegate struct { + _TableViewSetObjectValueForTableColumnRow func(tableView appkit.TableView, object objc.Object, tableColumn appkit.TableColumn, row int) + _NumberOfRowsInTableView func(tableView appkit.TableView) int + _TableViewSortDescriptorsDidChange func(tableView appkit.TableView, oldDescriptors []foundation.SortDescriptor) + _TableViewDraggingSessionEndedAtPointOperation func(tableView appkit.TableView, session appkit.DraggingSession, screenPoint foundation.Point, operation appkit.DragOperation) + _TableViewDraggingSessionWillBeginAtPointForRowIndexes func(tableView appkit.TableView, session appkit.DraggingSession, screenPoint foundation.Point, rowIndexes foundation.IndexSet) + _TableViewAcceptDropRowDropOperation func(tableView appkit.TableView, info appkit.DraggingInfoObject, row int, dropOperation appkit.TableViewDropOperation) bool + _TableViewObjectValueForTableColumnRow func(tableView appkit.TableView, tableColumn appkit.TableColumn, row int) objc.Object + _TableViewPasteboardWriterForRow func(tableView appkit.TableView, row int) appkit.PasteboardWritingObject + _TableViewUpdateDraggingItemsForDrag func(tableView appkit.TableView, draggingInfo appkit.DraggingInfoObject) + _TableViewValidateDropProposedRowProposedDropOperation func(tableView appkit.TableView, info appkit.DraggingInfoObject, row int, dropOperation appkit.TableViewDropOperation) appkit.DragOperation +} + +func (t *TableViewDataSourceDelegate) TableViewSetObjectValueForTableColumnRow(tableView appkit.TableView, object objc.Object, tableColumn appkit.TableColumn, row int) { + t._TableViewObjectValueForTableColumnRow(tableView, tableColumn, row) +} + +func (t *TableViewDataSourceDelegate) HasTableViewSetObjectValueForTableColumnRow() bool { + if t._TableViewSetObjectValueForTableColumnRow != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewSetObjectValueForTableColumnRow(f func(tableView appkit.TableView, object objc.Object, tableColumn appkit.TableColumn, row int)) { + t._TableViewSetObjectValueForTableColumnRow = f +} + +func (t *TableViewDataSourceDelegate) NumberOfRowsInTableView(tableView appkit.TableView) int { + return t._NumberOfRowsInTableView(tableView) +} + +func (t *TableViewDataSourceDelegate) HasNumberOfRowsInTableView() bool { + if t._NumberOfRowsInTableView != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetNumberOfRowsInTableView(f func(tableView appkit.TableView) int) { + t._NumberOfRowsInTableView = f +} + +func (t *TableViewDataSourceDelegate) TableViewSortDescriptorsDidChange(tableView appkit.TableView, oldDescriptors []foundation.SortDescriptor) { + t._TableViewSortDescriptorsDidChange(tableView, oldDescriptors) +} + +func (t *TableViewDataSourceDelegate) HasTableViewSortDescriptorsDidChange() bool { + if t._TableViewSortDescriptorsDidChange != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewSortDescriptorsDidChange(f func(tableView appkit.TableView, oldDescriptors []foundation.SortDescriptor)) { + t._TableViewSortDescriptorsDidChange = f +} + +func (t *TableViewDataSourceDelegate) TableViewDraggingSessionEndedAtPointOperation(tableView appkit.TableView, session appkit.DraggingSession, screenPoint foundation.Point, operation appkit.DragOperation) { + t._TableViewDraggingSessionEndedAtPointOperation(tableView, session, screenPoint, operation) +} + +func (t *TableViewDataSourceDelegate) HasTableViewDraggingSessionEndedAtPointOperation() bool { + if t._TableViewDraggingSessionEndedAtPointOperation != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewDraggingSessionEndedAtPointOperation(f func(tableView appkit.TableView, session appkit.DraggingSession, screenPoint foundation.Point, operation appkit.DragOperation)) { + t._TableViewDraggingSessionEndedAtPointOperation = f +} + +func (t *TableViewDataSourceDelegate) TableViewDraggingSessionWillBeginAtPointForRowIndexes(tableView appkit.TableView, session appkit.DraggingSession, screenPoint foundation.Point, rowIndexes foundation.IndexSet) { + t._TableViewDraggingSessionWillBeginAtPointForRowIndexes(tableView, session, screenPoint, rowIndexes) +} + +func (t *TableViewDataSourceDelegate) HasTableViewDraggingSessionWillBeginAtPointForRowIndexes() bool { + if t._TableViewDraggingSessionWillBeginAtPointForRowIndexes != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewDraggingSessionWillBeginAtPointForRowIndexes(f func(tableView appkit.TableView, session appkit.DraggingSession, screenPoint foundation.Point, rowIndexes foundation.IndexSet)) { + t._TableViewDraggingSessionWillBeginAtPointForRowIndexes = f +} + +func (t *TableViewDataSourceDelegate) TableViewAcceptDropRowDropOperation(tableView appkit.TableView, info appkit.DraggingInfoObject, row int, dropOperation appkit.TableViewDropOperation) bool { + return t._TableViewAcceptDropRowDropOperation(tableView, info, row, dropOperation) +} + +func (t *TableViewDataSourceDelegate) HasTableViewAcceptDropRowDropOperation() bool { + if t._TableViewAcceptDropRowDropOperation != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewAcceptDropRowDropOperation(f func(tableView appkit.TableView, info appkit.DraggingInfoObject, row int, dropOperation appkit.TableViewDropOperation) bool) { + t._TableViewAcceptDropRowDropOperation = f +} + +func (t *TableViewDataSourceDelegate) TableViewObjectValueForTableColumnRow(tableView appkit.TableView, tableColumn appkit.TableColumn, row int) objc.Object { + return t._TableViewObjectValueForTableColumnRow(tableView, tableColumn, row) +} + +func (t *TableViewDataSourceDelegate) HasTableViewObjectValueForTableColumnRow() bool { + if t._TableViewObjectValueForTableColumnRow != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewObjectValueForTableColumnRow(f func(tableView appkit.TableView, tableColumn appkit.TableColumn, row int) objc.Object) { + t._TableViewObjectValueForTableColumnRow = f +} + +func (t *TableViewDataSourceDelegate) TableViewPasteboardWriterForRow(tableView appkit.TableView, row int) appkit.PasteboardWritingObject { + return t._TableViewPasteboardWriterForRow(tableView, row) +} + +func (t *TableViewDataSourceDelegate) HasTableViewPasteboardWriterForRow() bool { + if t._TableViewPasteboardWriterForRow != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewPasteboardWriterForRow(f func(tableView appkit.TableView, row int) appkit.PasteboardWritingObject) { + t._TableViewPasteboardWriterForRow = f +} + +func (t *TableViewDataSourceDelegate) TableViewUpdateDraggingItemsForDrag(tableView appkit.TableView, draggingInfo appkit.DraggingInfoObject) { + t._TableViewUpdateDraggingItemsForDrag(tableView, draggingInfo) +} + +func (t *TableViewDataSourceDelegate) HasTableViewUpdateDraggingItemsForDrag() bool { + if t._TableViewUpdateDraggingItemsForDrag != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewUpdateDraggingItemsForDrag(f func(tableView appkit.TableView, draggingInfo appkit.DraggingInfoObject)) { + t._TableViewUpdateDraggingItemsForDrag = f +} + +func (t *TableViewDataSourceDelegate) TableViewValidateDropProposedRowProposedDropOperation(tableView appkit.TableView, info appkit.DraggingInfoObject, row int, dropOperation appkit.TableViewDropOperation) appkit.DragOperation { + return t._TableViewValidateDropProposedRowProposedDropOperation(tableView, info, row, dropOperation) +} + +func (t *TableViewDataSourceDelegate) HasTableViewValidateDropProposedRowProposedDropOperation() bool { + if t._TableViewValidateDropProposedRowProposedDropOperation != nil { + return true + } + return false +} + +func (t *TableViewDataSourceDelegate) SetTableViewValidateDropProposedRowProposedDropOperation(f func(tableView appkit.TableView, info appkit.DraggingInfoObject, row int, dropOperation appkit.TableViewDropOperation) appkit.DragOperation) { + t._TableViewValidateDropProposedRowProposedDropOperation = f +} diff --git a/macos/appkit/enumtypes.gen.go b/macos/appkit/enumtypes.gen.go index d5dd4d31..d47788ad 100644 --- a/macos/appkit/enumtypes.gen.go +++ b/macos/appkit/enumtypes.gen.go @@ -3004,7 +3004,7 @@ const ( // Layout priority used to indicate the relative importance of constraints, allowing Auto Layout to make appropriate tradeoffs when satisfying the constraints of the system as a whole. [Full Topic] // // [Full Topic]: https://developer.apple.com/documentation/appkit/nslayoutpriority?language=objc -type LayoutPriority float64 +type LayoutPriority float32 const ( LayoutPriorityDefaultHigh LayoutPriority = 750.000000