-
-
Notifications
You must be signed in to change notification settings - Fork 285
feat: Optimize export dialog with NSOutlineView for 60fps performance #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
da21017
feat: optimize export dialog with NSOutlineView for 60fps performance
datlechin f18fe9b
Update TablePro/Views/Export/ExportTableCellViews.swift
datlechin cb2dbea
Update TablePro/Views/Export/ExportTableOutlineView.swift
datlechin 6864143
Update TablePro/Views/Export/ExportTableOutlineView.swift
datlechin 84af522
Update TablePro/Views/Export/ExportTableOutlineView.swift
datlechin 1014c0d
Update TablePro/Views/Export/ExportTableCellViews.swift
datlechin 5c12729
Update TablePro/Views/Export/ExportTableOutlineView.swift
datlechin bbf10fd
Update TablePro/Views/Export/ExportTableOutlineView.swift
datlechin 0a5821a
Optimize export dialog performance
datlechin 7e2365e
wip
datlechin 293f12c
Update TablePro/Views/Export/ExportTableCellViews.swift
datlechin e6dff20
Update TablePro/Views/Export/ExportTableOutlineView.swift
datlechin 14d9ee2
Address PR review feedback
datlechin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,233 @@ | ||
| // | ||
| // ExportTableCellViews.swift | ||
| // TablePro | ||
| // | ||
| // Custom NSTableCellView implementations for export table outline view. | ||
| // Provides high-performance cell reuse for database and table rows. | ||
| // | ||
|
|
||
| import AppKit | ||
| import SwiftUI | ||
|
|
||
| // MARK: - Database Row Cell | ||
|
|
||
| /// Cell view for database rows with tristate checkbox and name | ||
| final class DatabaseRowCellView: NSTableCellView { | ||
|
|
||
| private let checkbox: NSButton | ||
| private let iconView: NSImageView | ||
| private let nameLabel: NSTextField | ||
|
|
||
| var checkboxAction: ((NSButton) -> Void)? | ||
|
|
||
| override init(frame frameRect: NSRect) { | ||
| // Create checkbox | ||
| checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil) | ||
| checkbox.allowsMixedState = true | ||
| checkbox.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| // Create icon | ||
| iconView = NSImageView() | ||
| iconView.image = NSImage(systemSymbolName: "cylinder", accessibilityDescription: "Database") | ||
| iconView.contentTintColor = .systemBlue | ||
| iconView.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| // Create name label | ||
| nameLabel = NSTextField(labelWithString: "") | ||
| nameLabel.font = .systemFont(ofSize: 13) | ||
| nameLabel.lineBreakMode = .byTruncatingMiddle | ||
| nameLabel.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| super.init(frame: frameRect) | ||
|
|
||
| addSubview(checkbox) | ||
| addSubview(iconView) | ||
| addSubview(nameLabel) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| // Checkbox | ||
| checkbox.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2), | ||
| checkbox.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| checkbox.widthAnchor.constraint(equalToConstant: 16), | ||
|
|
||
| // Icon | ||
| iconView.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: 3), | ||
| iconView.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| iconView.widthAnchor.constraint(equalToConstant: 16), | ||
| iconView.heightAnchor.constraint(equalToConstant: 16), | ||
|
|
||
| // Name | ||
| nameLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 3), | ||
| nameLabel.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| nameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2), | ||
| ]) | ||
|
|
||
| checkbox.target = self | ||
| checkbox.action = #selector(checkboxToggled(_:)) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| @objc private func checkboxToggled(_ sender: NSButton) { | ||
| checkboxAction?(sender) | ||
| } | ||
|
|
||
| func configure(database: ExportDatabaseItem, action: @escaping (NSButton) -> Void) { | ||
| nameLabel.stringValue = database.name | ||
| checkboxAction = action | ||
|
|
||
| // Calculate tristate based on table selection | ||
| if database.tables.isEmpty { | ||
| // Explicitly handle databases with no tables: keep visual "off" but disable interaction | ||
| checkbox.state = .off | ||
| checkbox.isEnabled = false | ||
| } else { | ||
| let selectedCount = database.tables.filter(\.isSelected).count | ||
| if selectedCount == 0 { | ||
| checkbox.state = .off | ||
| } else if selectedCount == database.tables.count { | ||
| checkbox.state = .on | ||
| } else { | ||
| checkbox.state = .mixed | ||
| } | ||
| checkbox.isEnabled = true | ||
| } | ||
|
|
||
| checkbox.setAccessibilityLabel("Select database \(database.name)") | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Table Row Cell | ||
|
|
||
| /// Cell view for table rows with selection checkbox, name, and optional SQL options | ||
| final class TableRowCellView: NSTableCellView { | ||
|
|
||
| private let selectionCheckbox: NSButton | ||
| private let iconView: NSImageView | ||
| private let nameLabel: NSTextField | ||
|
|
||
| var selectionAction: ((NSButton) -> Void)? | ||
|
|
||
| override init(frame frameRect: NSRect) { | ||
| // Create selection checkbox | ||
| selectionCheckbox = NSButton(checkboxWithTitle: "", target: nil, action: nil) | ||
| selectionCheckbox.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| // Create icon | ||
| iconView = NSImageView() | ||
| iconView.image = NSImage(systemSymbolName: "tablecells", accessibilityDescription: "Table") | ||
| iconView.contentTintColor = .systemGray | ||
| iconView.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| // Create name label | ||
| nameLabel = NSTextField(labelWithString: "") | ||
| nameLabel.font = .systemFont(ofSize: 13) | ||
| nameLabel.lineBreakMode = .byTruncatingMiddle | ||
| nameLabel.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| super.init(frame: frameRect) | ||
|
|
||
| addSubview(selectionCheckbox) | ||
| addSubview(iconView) | ||
| addSubview(nameLabel) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| // Selection checkbox (NSOutlineView handles indentation) | ||
| selectionCheckbox.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2), | ||
| selectionCheckbox.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| selectionCheckbox.widthAnchor.constraint(equalToConstant: 16), | ||
|
|
||
| // Icon | ||
| iconView.leadingAnchor.constraint(equalTo: selectionCheckbox.trailingAnchor, constant: 3), | ||
| iconView.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| iconView.widthAnchor.constraint(equalToConstant: 16), | ||
| iconView.heightAnchor.constraint(equalToConstant: 16), | ||
|
|
||
| // Name | ||
| nameLabel.leadingAnchor.constraint(equalTo: iconView.trailingAnchor, constant: 3), | ||
| nameLabel.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| nameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2), | ||
| ]) | ||
|
|
||
| selectionCheckbox.target = self | ||
| selectionCheckbox.action = #selector(selectionToggled(_:)) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| @objc private func selectionToggled(_ sender: NSButton) { | ||
| selectionAction?(sender) | ||
| } | ||
|
|
||
| func configure(table: ExportTableItem, selectionAction: @escaping (NSButton) -> Void) { | ||
| nameLabel.stringValue = table.name | ||
| selectionCheckbox.state = table.isSelected ? .on : .off | ||
| self.selectionAction = selectionAction | ||
| selectionCheckbox.setAccessibilityLabel("Select table \(table.name)") | ||
|
|
||
| // Update icon based on whether this item is a view or a regular table | ||
| if #available(macOS 11.0, *) { | ||
| let symbolName: String | ||
| let tintColor: NSColor | ||
|
|
||
| if table.type == .view { | ||
| symbolName = "eye" | ||
| tintColor = .systemPurple | ||
| } else { | ||
| symbolName = "tablecells" | ||
| tintColor = .systemGray | ||
| } | ||
|
|
||
| if let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil) { | ||
| iconView.image = image | ||
| iconView.contentTintColor = tintColor | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // MARK: - SQL Option Cell | ||
|
|
||
| /// Cell view for SQL option columns (Structure, Drop, Data) | ||
| final class SQLOptionCellView: NSTableCellView { | ||
| private let checkbox: NSButton | ||
|
|
||
| var checkboxAction: ((NSButton) -> Void)? | ||
|
|
||
| override init(frame frameRect: NSRect) { | ||
| checkbox = NSButton(checkboxWithTitle: "", target: nil, action: nil) | ||
| checkbox.translatesAutoresizingMaskIntoConstraints = false | ||
|
|
||
| super.init(frame: frameRect) | ||
|
|
||
| addSubview(checkbox) | ||
|
|
||
| NSLayoutConstraint.activate([ | ||
| checkbox.centerXAnchor.constraint(equalTo: centerXAnchor), | ||
| checkbox.centerYAnchor.constraint(equalTo: centerYAnchor), | ||
| checkbox.widthAnchor.constraint(equalToConstant: 16), | ||
| ]) | ||
|
|
||
| checkbox.target = self | ||
| checkbox.action = #selector(checkboxToggled(_:)) | ||
| } | ||
|
|
||
| required init?(coder: NSCoder) { | ||
| fatalError("init(coder:) has not been implemented") | ||
| } | ||
|
|
||
| @objc private func checkboxToggled(_ sender: NSButton) { | ||
| checkboxAction?(sender) | ||
| } | ||
|
|
||
| func configure(isChecked: Bool, isEnabled: Bool, action: @escaping (NSButton) -> Void) { | ||
| checkbox.state = isChecked ? .on : .off | ||
| checkbox.isEnabled = isEnabled | ||
| checkbox.alphaValue = isEnabled ? 1.0 : 0.4 | ||
| checkboxAction = action | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.