4 điểm bởi GN⁺ 2024-05-26 | 1 bình luận | Chia sẻ qua WhatsApp
  • 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
  • spot là 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/ui là 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ằng spot.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
  • 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
  • spot là 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 interface spot.Control
  • spot/ui là 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.RenderContext làm đối số đầu tiên rồi gọi spot.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…
  • 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
  • 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.Component và quản lý widget native
  • Hiện chưa hỗ trợ dùng spot/ui vớ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ọi spot.Make cù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.Build hoặc truyền hàm render vào spot.BuildFn để thực thi
  • Mount: quá trình tạo control UI thực từ cây control ảo
    • Có thể gọi Mount của nút trong cây hoặc dùng spot.Mount, spot.MountFn
  • Update: quá trình cập nhật cây control đã được mount
    • Thực hiện bằng cách gọi Update của nút trong cây

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_ButtonNSButton
    • Checkbox: control chọn một trong hai trạng thái, dùng Fl_Check_ButtonNSButton
    • Dropdown: dropdown chọn một mục trong nhiều mục, dùng Fl_ChoiceNSComboBox
    • Image: control hiển thị ảnh bitmap, dùng Fl_BoxNSButton tùy chỉnh
    • Label: nhãn văn bản không thể chỉnh sửa, dùng Fl_BoxNSTextField
    • ListBox: control danh sách chọn đơn hoặc đa chọn, dùng Fl_Select_Browser/Fl_Multi_BrowserNSTableView
    • ProgressBar: hiển thị tiến độ của tác vụ dài, dùng Fl_ProgressNSProgressIndicator
    • Slider: input thanh trượt ngang, dùng Fl_SliderNSSlider
    • Spinner: ô nhập số có nút tăng/giảm, dùng Fl_SpinnerNSTextField+NSStepper
    • TextField: ô nhập văn bản một dòng, dùng Fl_InputNSTextField
    • TextEditor: chỉnh sửa văn bản nhiều dòng, dùng Fl_Text_EditorNSTextView
    • Window: control cửa sổ cấp cao nhất, dùng Fl_WindowNSWindow
  • 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

 
GN⁺ 2024-05-26
Ý 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

    • go-fltk build và chạy được trên Windows, và thực tế hoạt động khá ổ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
    • Mình rất thích WxWindows, nhưng dạo này đã lún quá sâu vào Go rồi
      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
    • Có wxGo, nhưng tiếc là dự án này không còn được bảo trì nữa
  • 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

    • Trong các tình huống phức tạp, ví dụ khi người dùng đang tương tác với UI đồng thời một tác vụ chạy lâu ở nền cũng làm thay đổi trạng thái UI, việc quản lý trạng thái rất nhanh sẽ trở nên vượt tầm kiểm soát
      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?

    • Có vẻ trên Windows họ dùng FLTK, chỉ là chưa có hỗ trợ native thôi
    • Dùng WSL là được
  • 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

    • Mình cũng gặp y hệt vấn đề đó. Kiểu ghép composition component trong React thật sự rất hay và rất khó quay lại cách cũ
      Cuối cùng mình đã tìm ra cách làm thứ tương tự chỉ với html/template chuẩn của Go, và có viết lại ở đây: https://www.sheshbabu.com/posts/react-like-composition-using...
    • Mình tò mò 3 tuần trước thì bạn đã dùng gì thay thế
  • 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à QtReact 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

    • Nếu có sẵn môi trường phù hợp như trình biên dịch C thích hợp, Spot vẫn hoạt động trên Windows mà không cần thay đổi gì và chỉ cần chọn backend FLTK
      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