Spot - Bộ công cụ GUI desktop cho Go, tương tự React
(github.com/roblillack)- Spot là một bộ công cụ GUI desktop đa nền tảng đơn giản để xây dựng ứng dụng trong Go, sử dụng widget native khi có thể và cung cấp API nhất quán giữa các nền tảng
- Khi trạng thái ứng dụng thay đổi, nó tạo lại cây component bất biến rồi so sánh với trạng thái trước đó để quyết định những control UI nào cần được cập nhật
- Backend hiện tại gồm triển khai dựa trên Cocoa cho macOS và FLTK cho các nền tảng khác, đồng thời trên macOS cũng có thể tùy chọn dùng FLTK
spotlà gói lõi không phụ thuộc backend, cung cấp mô hình phản ứng và khả năng render;spot/uilà tập hợp các control GUI đa nền tảng được dựng sẵn- Hiện chưa cung cấp tự động bố cục, nhiều cửa sổ, hộp thoại modal, cửa sổ đổi kích thước được, thanh menu, widget tùy biến, truy cập widget native, kéo thả và quốc tế hóa
Mục đích và mô hình cơ bản của Spot
- Spot là một bộ công cụ GUI phản ứng cho Go, được thiết kế để dùng widget native ở những nơi có thể đồng thời cung cấp API nhất quán trên nhiều nền tảng
- Có thể thêm vào dự án như một dependency đơn giản và chỉ cần viết mã Go để tạo ra binary GUI native tự chứa, không cần công cụ bổ sung hay sinh mã
- Ví dụ tạo cửa sổ và nút theo luồng
ui.Init(),spot.MountFn(...),ui.Run(), và quản lý trạng thái số lần nhấp bằngspot.UseState[int](<https://github.com/roblillack/ctx, 0>) - Trình xử lý sự kiện nhấp nút gọi
setCounter(counter + 1), và khi trạng thái thay đổi thì tiêu đề nút sẽ đổi theo định dạng"Clicked %d times!"
Cách cập nhật theo mô hình phản ứng
- Trong Spot, reactive có nghĩa là UI sẽ tự động được cập nhật khi trạng thái ứng dụng thay đổi
- Khi trạng thái đổi, nó tạo lại cây component bất biến rồi so sánh nhanh với trạng thái trước đó để xác định control UI nào cần cập nhật
- Trên web, ý tưởng này thường được gọi là virtual DOM, và Spot bắt đầu từ một thử nghiệm mang khái niệm đó vào môi trường desktop Go để hiện thực một thư viện GUI tương tự React
- Thay vì cập nhật UI thủ công, lập trình viên quản lý logic ứng dụng và trạng thái bằng các hàm render không có side effect và các hook như
UseState
Backend và cấu trúc gói
- Spot tự động chọn backend phù hợp với nền tảng chạy tại thời điểm biên dịch
- Hiện có hai backend được cung cấp
- Triển khai dựa trên FLTK: sử dụng go-fltk
- Triển khai dựa trên Cocoa: sử dụng phiên bản đã chỉnh sửa của gocoa
- Trên macOS dùng backend Cocoa, còn các nền tảng khác dùng backend dựa trên FLTK
- Trên macOS cũng có thể tùy chọn dùng FLTK, còn việc cải thiện hỗ trợ Windows vẫn là kế hoạch trong tương lai
spotlà gói lõi cung cấp mô hình phản ứng và khả năng render, có thể dùng với bất kỳ tập hợp control nào triển khai interfacespot.Controlspot/uilà gói control GUI đa nền tảng dựng sẵn có thể dùng cùng Spot
Component, control và hook
- Giống như React, có thể tạo hook tùy chỉnh
- Tạo một hàm nhận
*spot.RenderContextlàm đối số đầu tiên rồi gọispot.UseState,spot.UseEffect... để gắn vào vòng đời của Spot - Theo quy ước, tên hàm dùng tiền tố
Use…
- Tạo một hàm nhận
- Component tùy chỉnh có thể được tạo bằng struct triển khai interface
spot.Component- Interface này có một phương thức duy nhất là
Render(ctx *spot.RenderContext) spot.Component - Component tạo theo cách này có thể được dùng giống như component tích hợp sẵn
- Interface này có một phương thức duy nhất là
- Trong Spot, component là đơn vị logic chứa business logic và trạng thái
- Component được cấu thành từ các component khác và cuối cùng được render thành một hoặc nhiều control
- control là một component đặc biệt được mount vào cây UI và biểu diễn phần tử trực quan trên màn hình
- Thông thường dựa trên triển khai native của backend GUI, như nút, nhãn hay ô nhập văn bản
- Cũng có thể dùng một thư viện widget hoàn toàn khác với thư viện widget được cung cấp
- Chỉ cần tạo struct triển khai interface
spot.Componentvà quản lý widget native
- Chỉ cần tạo struct triển khai interface
- Hiện chưa hỗ trợ dùng
spot/uivới backend khác ngoài Cocoa hoặc FLTK
Thuật ngữ vòng đời render
- Make: quá trình tạo một instance struct triển khai interface
spot.Component, hoặc gọispot.Makecùng với hàm render để tạo instance component mới - Render: quá trình áp dụng trạng thái component vào các thành phần và trả về instance component khác
- Build: quá trình render component đệ quy để tạo cây control
- Có thể truyền instance component vào
spot.Buildhoặc truyền hàm render vàospot.BuildFnđể thực thi
- Có thể truyền instance component vào
- Mount: quá trình tạo control UI thực từ cây control ảo
- Có thể gọi
Mountcủa nút trong cây hoặc dùngspot.Mount,spot.MountFn
- Có thể gọi
- Update: quá trình cập nhật cây control đã được mount
- Thực hiện bằng cách gọi
Updatecủa nút trong cây
- Thực hiện bằng cách gọi
Những tính năng hiện chưa có
- Hiện tại Spot chưa cung cấp các tính năng sau
-
Tự động bố cục
- Nhiều cửa sổ
- Hộp thoại modal
- Cửa sổ có thể thay đổi kích thước
- Thanh menu
- Widget tùy biến
- Truy cập widget native
- Kéo và thả
- Quốc tế hóa
-
Các control UI được hỗ trợ
- Spot mặc định cung cấp nhiều control UI như nút, nhãn, ô nhập văn bản, thanh trượt, dropdown...
- Ký hiệu trạng thái hỗ trợ được chia thành ❓ chưa triển khai, 🚧 đang thực hiện, ⚠️ triển khai một phần, ✅ hoàn tất
- Các control chính đã hoàn tất gồm
- Button: nút thực thi hành động đơn giản, dùng
Fl_ButtonvàNSButton - Checkbox: control chọn một trong hai trạng thái, dùng
Fl_Check_ButtonvàNSButton - Dropdown: dropdown chọn một mục trong nhiều mục, dùng
Fl_ChoicevàNSComboBox - Image: control hiển thị ảnh bitmap, dùng
Fl_BoxvàNSButtontùy chỉnh - Label: nhãn văn bản không thể chỉnh sửa, dùng
Fl_BoxvàNSTextField - ListBox: control danh sách chọn đơn hoặc đa chọn, dùng
Fl_Select_Browser/Fl_Multi_BrowservàNSTableView - ProgressBar: hiển thị tiến độ của tác vụ dài, dùng
Fl_ProgressvàNSProgressIndicator - Slider: input thanh trượt ngang, dùng
Fl_SlidervàNSSlider - Spinner: ô nhập số có nút tăng/giảm, dùng
Fl_SpinnervàNSTextField+NSStepper - TextField: ô nhập văn bản một dòng, dùng
Fl_InputvàNSTextField - TextEditor: chỉnh sửa văn bản nhiều dòng, dùng
Fl_Text_EditorvàNSTextView - Window: control cửa sổ cấp cao nhất, dùng
Fl_WindowvàNSWindow
- Button: nút thực thi hành động đơn giản, dùng
- Cũng có các control mới triển khai một phần hoặc chưa triển khai
- Dial: control trạng thái hình tròn và hiện ở mức ⚠️ triển khai một phần
- ComboBox: menu dropdown kết hợp nhập văn bản và vẫn chưa được bắt đầu
- Một ứng viên backend tiềm năng trong tương lai là thư viện control Windows native
https://github.com/rodrigocfd/windigo
1 bình luận
Ý kiến trên Hacker News
Cái này nhất định phải xem. Mình đang tìm một cách đơn giản để làm công cụ phát triển nội bộ bằng Go, và về cơ bản nhu cầu chỉ ở mức form có nút bấm và ô văn bản
Mình cũng đã thử Gio nhưng khá khó hiểu, còn hiện tại đang dùng wails và thấy thích hơn nhiều. Dự án này cũng rất thú vị và có vẻ đáng để tìm hiểu
Mình thực sự khuyên nên thu hẹp định hướng kiểu “Đa nền tảng: tận dụng FLTK[1] và Cocoa[2], Spot chạy trên Mac, Linux, BSD và có kế hoạch hỗ trợ Windows native trong tương lai”
Vẫn có thể giữ lại những gì đã học để đảm bảo tính linh hoạt về sau, nhưng trước tiên nên làm thật tốt với một toolkit. Toolkit GUI, binding GUI, và bản thân GUI vốn đã rất dễ chết chìm trong chi tiết; nếu còn tự nguyện gánh thêm cả chi tiết của nhiều toolkit nền tảng khác nhau, rất có thể cuối cùng sẽ không làm tốt được toolkit nào cả
Có câu “90% đầu tiên chiếm 90% công việc, còn 10% cuối lại chiếm thêm 90% nữa”, nhưng với GUI thì ngay cả câu đó còn có vẻ quá lạc quan. 10% đầu tiên đã là 90% công việc, 10% tiếp theo gấp 10 lần như vậy, và 10% sau nữa lại gấp 10 lần tiếp. Nếu cố làm đa nền tảng thì rất dễ tự siết cổ mình
Mình không mong bạn đồng ý ngay lúc này, nhưng sau này nếu gặp cảnh ba toolkit buộc bạn phải xử lý thứ như rich text theo ba cách mâu thuẫn nhau, thì mong bạn tự cho phép mình giữ lại toolkit nền tảng được hỗ trợ tốt nhất hoặc phổ biến nhất, và bỏ phần còn lại đi
Mình đã tìm thứ kiểu này trong Go khá lâu rồi. Go có quy trình build đơn giản, nên mình nghĩ nó có cơ hội lớn mang lại trải nghiệm lập trình viên rất tốt cho UI đa nền tảng
Theo kinh nghiệm của mình, một nửa nỗi đau của phát triển đa nền tảng là quản lý độ phức tạp của build, và Go gần như loại bỏ được chuyện đó
Tuy vậy, kích thước mặc định của control native lại khác nhau giữa các nền tảng, nên mình khá tò mò Spot sẽ giải quyết bố cục đa nền tảng thế nào. Ngay cả ở các toolkit đa nền tảng khác mình cũng chưa thấy chỗ nào xử lý thực sự tốt. Dù sao thì chúc may mắn
Mình đã tìm thứ như vậy từ vài năm trước. Chỉ là mình cũng cần hỗ trợ Windows. Cuối cùng mình chuyển sang C++ để dùng wxWidgets, và có được một binary tự chứa nhỏ gọn
Đây là toolkit native, nhưng mình rất ấn tượng khi thấy FLTK hỗ trợ phóng to/thu nhỏ toàn bộ ứng dụng bằng Ctrl-+ và Ctrl+- như trình duyệt. Ngoài ra, nhờ https://github.com/fltk-rs/fltk-theme?tab=readme-ov-file#wid... mà mình có ấn tượng tốt hơn về việc FLTK có thể trông “native” tới mức nào
Nhân tiện, gần đây mình cũng phát hiện ra GoVCL https://z-kit.cc/en/ và muốn thử một lần
Bản Spot “Hello World” tự chứa có kích thước 2.3MiB trên máy Mac của mình. Không đẹp lắm nhưng với mình là đủ dùng
Mình tò mò lợi ích của cách tiếp cận cây control ảo là gì, so với việc tự cập nhật trực tiếp các control hiển thị cho người dùng
Bạn sẽ phải viết code callback khắp nơi, và mỗi callback có thể phải cẩn thận kiểm tra trạng thái hiện tại của mọi hoạt động khác trước khi cập nhật hàng chục widget
Với cách tiếp cận phản ứng, bạn viết một hàm render duy nhất mô tả giao diện cho một trạng thái nhất định, còn framework sẽ lo khi nào gọi nó và truyền vào dữ liệu gì. Cách này dễ hiểu hơn nhiều, và sau khi đã dùng React thì rất khó quay lại, nên mình bắt đầu thử xem liệu có thể làm thứ tương tự trong Go hay không
Trông hay đấy. Không biết có thể thêm các nền tảng được hỗ trợ vào README không
Những thông tin như Windows, Linux, macOS, *BSD, Android, iOS, Web, Tizen sẽ khá thú vị
Có thể trình bày kiểu như tài liệu Flutter cũng được: https://docs.flutter.dev/reference/supported-platforms
Nỗ lực thì đáng khen, nhưng đa nền tảng mà không có hỗ trợ Windows sao?
Giá mà mình biết cái này từ 3 tuần trước, hoặc theo lịch sử commit thì giá mà lúc đó nó đã tồn tại. Mình đã nói từ lâu rằng React được port sang Go hoặc một framework kiểu React cho Go sẽ mang lại trải nghiệm phát triển cực tốt, và cái này có vẻ khớp hoàn toàn
Trước khi nhận ra điều đó qua React.lua, mình từng khá ghét React.js
Cuối cùng mình đã tìm ra cách làm thứ tương tự chỉ với
html/templatechuẩn của Go, và có viết lại ở đây: https://www.sheshbabu.com/posts/react-like-composition-using...Vấn đề lớn là khi phát hành cho desktop, thường rồi bạn cũng sẽ muốn có cả phiên bản web. Điều này đặc biệt đúng trừ khi đó là một ứng dụng rất ngách và tương tác rất nhiều với hệ điều hành
Hoặc bạn sẽ cần thứ gì đó nhắm tới nhiều nền tảng, bao gồm cả mobile
Mình đã tìm khá lâu, và thứ gần nhất mình thấy là Qt và React Native, nhưng cả hai đều là những lựa chọn đau đầu vì nhiều lý do khác nhau
FLTK có hỗ trợ Windows. Mình thắc mắc liệu việc chưa hỗ trợ Windows là do họ đang định dùng một giải pháp khác hay không
Tuy vậy, mục tiêu của mình là triển khai backend dựa trên Win32, dù mức ưu tiên chưa cao, và bước đầu tiên đã xong: https://github.com/roblillack/spot/pull/4