4 คะแนน โดย GN⁺ 2024-05-26 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Spot เป็นชุดเครื่องมือแบบ reactive ที่เรียบง่ายสำหรับสร้าง GUI เดสก์ท็อปข้ามแพลตฟอร์ม ใน Go โดยใช้ native widget เมื่อเป็นไปได้ และมอบ API ที่สม่ำเสมอในแต่ละแพลตฟอร์ม
  • เมื่อสถานะของแอปพลิเคชันเปลี่ยน จะสร้าง immutable component tree ขึ้นใหม่ แล้วเปรียบเทียบกับสถานะก่อนหน้าเพื่อกำหนดว่าจะอัปเดต UI control ใด
  • ขณะนี้ backend คือการใช้งานที่อิงกับ Cocoa บน macOS และ FLTK บนแพลตฟอร์มอื่น โดยบน macOS ก็สามารถเลือกใช้ FLTK ได้เช่นกัน
  • spot คือ core package ที่ไม่ขึ้นกับ backend ซึ่งให้โมเดลแบบ reactive และการเรนเดอร์ ส่วน spot/ui คือชุด GUI control ข้ามแพลตฟอร์มที่เตรียมไว้ล่วงหน้า
  • ยังไม่รองรับ auto layout, หลายหน้าต่าง, modal dialog, หน้าต่างที่ปรับขนาดได้, menu bar, custom widget, การเข้าถึง native widget, drag and drop และ internationalization

จุดประสงค์และโมเดลพื้นฐานของ Spot

  • Spot เป็น ชุดเครื่องมือ GUI แบบ reactive สำหรับ Go ที่ออกแบบมาเพื่อให้ API มีความสม่ำเสมอบนหลายแพลตฟอร์ม พร้อมใช้ native widget ในจุดที่ทำได้
  • เพียงเพิ่มเป็น dependency แบบธรรมดาในโปรเจ็กต์และเขียนโค้ด Go อย่างเดียว ก็สามารถสร้าง ไบนารี GUI แบบ native ที่บรรจุทุกอย่างในตัวเอง ได้ โดยไม่ต้องใช้เครื่องมือเสริมหรือการ generate code
  • ตัวอย่างใช้งานสร้างหน้าต่างและปุ่มด้วยลำดับ ui.Init(), spot.MountFn(...), ui.Run() และจัดการสถานะจำนวนครั้งที่คลิกด้วย spot.UseState[int](<https://github.com/roblillack/ctx, 0>)
  • ตัวจัดการเหตุการณ์คลิกของปุ่มจะเรียก setCounter(counter + 1) และเมื่อสถานะเปลี่ยน ชื่อปุ่มจะเปลี่ยนเป็นรูปแบบ "Clicked %d times!"

วิธีอัปเดตแบบ reactive

  • ใน Spot คำว่า reactive หมายถึง UI จะอัปเดตโดยอัตโนมัติเมื่อสถานะของแอปพลิเคชันเปลี่ยน
  • เมื่อสถานะเปลี่ยน จะสร้าง immutable component tree ขึ้นใหม่ แล้วเปรียบเทียบกับสถานะก่อนหน้าอย่างรวดเร็วเพื่อพิจารณาว่าควรอัปเดต UI control ใด
  • บนเว็บ แนวคิดนี้มักถูกเรียกว่า virtual DOM และ Spot นำแนวคิดนี้มาสู่สภาพแวดล้อมเดสก์ท็อปของ Go โดยเริ่มจากการทดลองสร้างไลบรารี GUI ที่คล้าย React
  • แทนที่นักพัฒนาจะต้องอัปเดต UI ด้วยตนเอง ก็สามารถจัดการ logic และสถานะของแอปพลิเคชันผ่านฟังก์ชันเรนเดอร์ที่ไม่มี side effect และ hook อย่าง UseState ได้

โครงสร้าง backend และแพ็กเกจ

  • Spot จะเลือก backend ที่เหมาะกับแพลตฟอร์มที่รันโดยอัตโนมัติขณะคอมไพล์
  • ปัจจุบันมี backend ที่ให้มาสองแบบ
  • บน macOS จะใช้ Cocoa backend และบนแพลตฟอร์มอื่นจะใช้ backend ที่อิงกับ FLTK
  • บน macOS ก็สามารถเลือกใช้ FLTK ได้เช่นกัน และการปรับปรุงการรองรับ Windows ยังเป็นแผนในอนาคต
  • spot คือ core package ที่ให้ โมเดลแบบ reactive และความสามารถด้านการเรนเดอร์ และสามารถใช้ร่วมกับชุด control ใดก็ได้ที่ implements อินเทอร์เฟซ spot.Control
  • spot/ui คือแพ็กเกจ GUI control ข้ามแพลตฟอร์ม ที่เตรียมไว้ให้ใช้งานร่วมกับ Spot

คอมโพเนนต์, คอนโทรล และฮุก

  • เช่นเดียวกับ React สามารถสร้าง custom hook ได้
    • สร้างฟังก์ชันที่รับ *spot.RenderContext เป็นอาร์กิวเมนต์ตัวแรก แล้วเรียก spot.UseState, spot.UseEffect เป็นต้น เพื่อเชื่อมเข้ากับ lifecycle ของ Spot
    • ตามธรรมเนียมจะตั้งชื่อฟังก์ชันขึ้นต้นด้วย Use…
  • custom component สามารถสร้างเป็น struct ที่ implements อินเทอร์เฟซ spot.Component
    • อินเทอร์เฟซนี้มีเมธอดเดียวคือ Render(ctx *spot.RenderContext) spot.Component
    • คอมโพเนนต์ที่สร้างด้วยวิธีนี้สามารถใช้งานได้แบบเดียวกับคอมโพเนนต์ที่มีมาในตัว
  • ใน Spot component คือหน่วยเชิงตรรกะที่เก็บ business logic และสถานะ
    • คอมโพเนนต์ประกอบขึ้นจากคอมโพเนนต์อื่น และท้ายที่สุดจะถูกเรนเดอร์เป็นหนึ่งหรือหลายคอนโทรล
  • control คือคอมโพเนนต์ชนิดพิเศษที่ถูก mount ลงใน UI tree และแทนองค์ประกอบที่มองเห็นได้บนหน้าจอ
    • โดยทั่วไปจะอิงกับ native implementation ของ GUI backend เช่น ปุ่ม, label, text input
  • สามารถใช้ไลบรารี widget ที่แตกต่างจากไลบรารี widget ที่มีให้โดยสิ้นเชิงได้เช่นกัน
    • เพียงสร้าง struct ที่ implements อินเทอร์เฟซ spot.Component และจัดการ native widget เอง
  • ขณะนี้ยังไม่รองรับการใช้ spot/ui ร่วมกับ backend อื่นนอกเหนือจาก Cocoa หรือ FLTK

คำศัพท์ของ lifecycle การเรนเดอร์

  • Make: กระบวนการสร้างอินสแตนซ์ struct ที่ implements อินเทอร์เฟซ spot.Component หรือสร้างอินสแตนซ์คอมโพเนนต์ใหม่ด้วยการเรียก spot.Make พร้อมฟังก์ชันเรนเดอร์
  • Render: กระบวนการนำสถานะของคอมโพเนนต์ไปใช้กับองค์ประกอบย่อยและคืนค่าอินสแตนซ์ของคอมโพเนนต์อื่น
  • Build: กระบวนการเรนเดอร์คอมโพเนนต์แบบ recursive เพื่อสร้าง control tree
    • ทำได้โดยส่งอินสแตนซ์คอมโพเนนต์ให้ spot.Build หรือส่งฟังก์ชันเรนเดอร์ให้ spot.BuildFn
  • Mount: กระบวนการสร้าง UI control จริงจาก virtual control tree
    • สามารถเรียก Mount ของ tree node หรือใช้ spot.Mount, spot.MountFn ได้
  • Update: กระบวนการอัปเดต control tree ที่ถูก mount แล้ว
    • ดำเนินการโดยเรียก Update ของ tree node

ความสามารถที่ยังไม่มีในตอนนี้

  • ขณะนี้ Spot ยังไม่มีความสามารถต่อไปนี้
    • auto layout

      • หลายหน้าต่าง
      • modal dialog
      • หน้าต่างที่ปรับขนาดได้
      • menu bar
      • custom widget
      • การเข้าถึง native widget
      • drag and drop
      • internationalization

UI control ที่รองรับ

  • Spot มี UI control พื้นฐานหลายแบบ เช่น ปุ่ม, label, text input, slider, dropdown
  • สถานะการรองรับแบ่งเป็น ❓ ยังไม่ทำ, 🚧 กำลังทำ, ⚠️ ทำบางส่วน, ✅ เสร็จแล้ว
  • คอนโทรลหลักที่เสร็จสมบูรณ์มีดังนี้
    • Button: ปุ่มสำหรับเรียกใช้งาน action แบบง่าย ใช้ Fl_Button และ NSButton
    • Checkbox: คอนโทรลสำหรับเลือกหนึ่งในสองตัวเลือก ใช้ Fl_Check_Button และ NSButton
    • Dropdown: ดรอปดาวน์สำหรับเลือกหนึ่งรายการจากหลายรายการ ใช้ Fl_Choice และ NSComboBox
    • Image: คอนโทรลสำหรับแสดงภาพบิตแมป ใช้ Fl_Box และ NSButton แบบ custom
    • Label: label ข้อความที่แก้ไขไม่ได้ ใช้ Fl_Box และ NSTextField
    • ListBox: คอนโทรลรายการแบบเลือกเดี่ยวหรือหลายรายการ ใช้ Fl_Select_Browser/Fl_Multi_Browser และ NSTableView
    • ProgressBar: แสดงความคืบหน้าของงานที่ใช้เวลานาน ใช้ Fl_Progress และ NSProgressIndicator
    • Slider: อินพุตแบบสไลเดอร์แนวนอน ใช้ Fl_Slider และ NSSlider
    • Spinner: อินพุตตัวเลขพร้อมปุ่มขึ้น/ลง ใช้ Fl_Spinner และ NSTextField+NSStepper
    • TextField: อินพุตข้อความหนึ่งบรรทัด ใช้ Fl_Input และ NSTextField
    • TextEditor: แก้ไขข้อความหลายบรรทัด ใช้ Fl_Text_Editor และ NSTextView
    • Window: คอนโทรลหน้าต่างระดับบนสุด ใช้ Fl_Window และ NSWindow
  • ยังมีคอนโทรลที่ทำเพียงบางส่วนหรือยังไม่เริ่ม
    • Dial: คอนโทรลสถานะแบบวงกลม และอยู่ในสถานะ ⚠️ ทำบางส่วน
    • ComboBox: เมนูดรอปดาวน์ที่รวมการป้อนข้อความไว้ด้วย และยังไม่ได้เริ่มทำ
  • มีการเสนอไลบรารี native Windows control https://github.com/rodrigocfd/windigo เป็นตัวเลือก backend ที่อาจใช้ในอนาคต

1 ความคิดเห็น

 
GN⁺ 2024-05-26
ความคิดเห็นจาก Hacker News
  • อันนี้ต้องดูให้ได้เลย กำลังหาวิธีง่าย ๆ สำหรับสร้างเครื่องมือพัฒนาภายในด้วย Go อยู่ ซึ่งจริง ๆ แล้วก็แค่ระดับฟอร์มที่มีปุ่มกับช่องข้อความ
    เคยลองใช้ Gio แล้วแต่เข้าใจยาก ตอนนี้ใช้ wails อยู่และชอบมากกว่าเยอะ โปรเจ็กต์นี้ก็น่าสนใจและดูคุ้มค่าที่จะลองศึกษา

  • แนวทางที่ว่า “ข้ามแพลตฟอร์ม: ใช้ FLTK[1] และ Cocoa[2] ทำให้ Spot ทำงานได้บน Mac, Linux, BSD และมีแผนรองรับ Windows แบบเนทีฟในอนาคต” นั้น ผมแนะนำอย่างจริงจังให้ลดขอบเขตลง
    เก็บบทเรียนที่ได้ไว้เพื่อความยืดหยุ่นในอนาคตก็ได้ แต่ควรทำให้เก่งกับ ทูลคิตเดียว ก่อน ด้าน GUI toolkit, GUI binding และตัว GUI เองก็จมอยู่กับรายละเอียดได้ง่ายอยู่แล้ว ถ้ายังอาสาแบกรายละเอียดของทูลคิตพื้นฐานหลายตัวเข้าไปอีก สุดท้ายมีโอกาสสูงที่จะทำได้ไม่ดีสักตัว
    มีคำพูดว่า “90% แรกคือ 90% ของงาน และ 10% ที่เหลือก็เป็นอีก 90%” แต่กับ GUI นี่ดูยังมองโลกสวยเกินไปด้วยซ้ำ 10% แรกคือ 90% ของงาน 10% ถัดไปหนักขึ้น 10 เท่า และ 10% ต่อจากนั้นก็หนักขึ้นอีก 10 เท่า ถ้าพยายามทำแบบ ข้ามแพลตฟอร์ม อาจไปไม่รอดได้
    ไม่ได้คาดหวังว่าคุณจะเห็นด้วยทันที แต่ถ้าวันหนึ่งต้องเจอสถานการณ์ที่ทูลคิตทั้งสามบังคับวิธีจัดการ rich text ที่ขัดกันคนละแบบ ก็หวังว่าคุณจะอนุญาตให้ตัวเองเก็บไว้แค่ทูลคิตพื้นฐานที่รองรับดีที่สุดหรือได้รับความนิยมที่สุด แล้วทิ้งที่เหลือได้

  • ตามหาของแบบนี้ใน Go มาสักพักแล้ว ผมมองว่า Go มีโอกาสสูงมากที่จะมอบประสบการณ์นักพัฒนาที่ยอดเยี่ยมสำหรับ UI แบบข้ามแพลตฟอร์ม เพราะ กระบวนการ build เรียบง่าย
    จากประสบการณ์ ความเจ็บปวดของการพัฒนาข้ามแพลตฟอร์มครึ่งหนึ่งคือการจัดการความซับซ้อนของการ build ซึ่ง Go แทบจะตัดปัญหานี้ทิ้งไปได้เลย
    แต่ก็สงสัยว่าจะจัดการเรื่อง เลย์เอาต์ข้ามแพลตฟอร์ม อย่างไร ในเมื่อขนาดเริ่มต้นของคอนโทรลแบบเนทีฟต่างกันไปตามแพลตฟอร์ม ส่วนนี้แม้แต่ในทูลคิตข้ามแพลตฟอร์มอื่น ๆ ก็ยังไม่ค่อยเห็นใครทำได้ดีมากนัก ยังไงก็ขอให้โชคดี

  • เมื่อหลายปีก่อนผมก็ตามหาของแบบนี้อยู่เหมือนกัน แต่ตอนนั้นต้องการรองรับ Windows ด้วย สุดท้ายเลยเปลี่ยนไปใช้ C++ เพื่อใช้ wxWidgets และได้ไบนารีขนาดเล็กแบบ self-contained

    • go-fltk สามารถ build และรันบน Windows ได้ และจริง ๆ ก็ทำงานได้ค่อนข้างดี
      มันเป็นทูลคิตแบบเนทีฟ แต่ผมประทับใจที่ FLTK รองรับการซูมทั้งแอปด้วย Ctrl-+ และ Ctrl+- เหมือนเบราว์เซอร์ และ https://github.com/fltk-rs/fltk-theme?tab=readme-ov-file#wid... ก็ทำให้ผมรู้สึกดีกับระดับที่ทำให้ FLTK ดู “เนทีฟ” ได้
      ที่เกี่ยวข้องกัน ผมเพิ่งไปเจอ GoVCL https://z-kit.cc/en/ และอยากลองใช้ดูสักครั้ง
    • ผมชอบ WxWindows มาก แต่ทุกวันนี้ถลำลึกกับ Go ไปแล้ว
      Spot “Hello World” แบบ self-contained มีขนาด 2.3MiB บน Mac ของผม ไม่ได้สวยมาก แต่สำหรับผมใช้งานได้สบาย
    • มี wxGo อยู่เหมือนกัน แต่เสียดายที่โปรเจ็กต์ไม่ได้รับการดูแลต่อแล้ว
  • สงสัยว่าข้อดีของแนวทาง virtual control tree เมื่อเทียบกับการอัปเดตคอนโทรลที่แสดงต่อผู้ใช้โดยตรงคืออะไร

    • ในสถานการณ์ที่ซับซ้อน เช่น ผู้ใช้กำลังโต้ตอบกับ UI ขณะเดียวกันก็มีงานที่ใช้เวลานานทำงานอยู่เบื้องหลังและเปลี่ยนสถานะของ UI ไปด้วย การจัดการสถานะจะกลายเป็นเรื่องรับมือยากอย่างรวดเร็ว
      คุณต้องเขียน callback กระจายไปทั่ว และแต่ละ callback อาจต้องตรวจสอบสถานะปัจจุบันของกิจกรรมอื่น ๆ ทั้งหมดอย่างระมัดระวัง ก่อนจะอัปเดตวิดเจ็ตอีกหลายสิบตัว
      ในแนวทางแบบ reactive คุณเขียนฟังก์ชัน render เพียงจุดเดียวที่อธิบายอินเทอร์เฟซสำหรับสถานะที่กำหนด แล้วเฟรมเวิร์กจะจัดการเองว่าจะเรียกเมื่อไรและส่ง input อะไรให้ แบบนี้เข้าใจง่ายกว่ามาก และหลังจากเคยใช้ React แล้วก็มักย้อนกลับได้ยาก เลยอยากลองดูว่าใน Go จะทำอะไรคล้ายกันได้ไหม
  • ดูดีเลย แต่อยากรู้ว่าจะช่วยระบุ แพลตฟอร์มที่รองรับ ไว้ใน README ได้ไหม
    ข้อมูลอย่าง Windows, Linux, macOS, *BSD, Android, iOS, Web, Tizen น่าจะน่าสนใจมาก
    จะเขียนแบบเอกสารของ Flutter ก็ได้: https://docs.flutter.dev/reference/supported-platforms

  • ความพยายามน่าชื่นชม แต่ ข้ามแพลตฟอร์มที่ไม่รองรับ Windows เนี่ยนะ?

    • ดูเหมือนว่าใช้ FLTK บน Windows แค่ยังไม่มีการรองรับแบบเนทีฟเท่านั้น
    • ใช้ WSL ก็ได้
  • ถ้ารู้จักสิ่งนี้เมื่อ 3 สัปดาห์ก่อนก็คงดี หรือถ้าดูจากประวัติ commit แล้วมันมีอยู่ตอนนั้นก็คงดีเหมือนกัน ผมพูดมานานแล้วว่าถ้ามี React ที่พอร์ตมาเป็น Go หรือเฟรมเวิร์กแนว React สำหรับ Go ประสบการณ์การพัฒนาจะดีมาก และอันนี้ก็ดูตรงเป๊ะเลย
    ก่อนจะไปเข้าใจเรื่องนี้จาก React.lua ผมเคยไม่ชอบ React.js พอสมควร

    • ผมก็มีปัญหาแบบเดียวกัน วิธี ประกอบคอมโพเนนต์ ที่ทำใน React มันดีมากจริง ๆ และย้อนกลับไปยาก
      สุดท้ายผมหาวิธีทำอะไรคล้าย ๆ กันโดยใช้เพียง html/template มาตรฐานของ Go และเขียนรายละเอียดไว้ที่นี่: https://www.sheshbabu.com/posts/react-like-composition-using...
    • อยากรู้ว่าเมื่อ 3 สัปดาห์ก่อนคุณใช้ตัวไหนแทน
  • ปัญหาใหญ่คือพอปล่อยเป็นเดสก์ท็อปแล้วก็มักจะอยากได้ เวอร์ชันเว็บ ด้วย เว้นแต่ว่าจะเป็นแอปเฉพาะทางมาก ๆ ที่ต้องโต้ตอบกับระบบปฏิบัติการอย่างหนัก
    หรือไม่ก็ต้องการบางอย่างที่รองรับหลายแพลตฟอร์มรวมถึงมือถือไปเลย
    ผมหามาค่อนข้างนานแล้ว แต่ตัวที่ใกล้เคียงที่สุดคือ Qt กับ React Native ซึ่งทั้งคู่ก็เป็นตัวเลือกที่ชวนปวดหัวด้วยหลายเหตุผล

  • FLTK รองรับ Windows อยู่แล้ว เลยสงสัยว่ายังไม่รองรับ Windows เพราะมีแผนจะใช้ทางแก้อื่นหรือเปล่า

    • ถ้ามี environment พร้อม เช่น C compiler ที่เหมาะสม Spot ก็ทำงานบน Windows ได้โดยไม่ต้องแก้อะไรเลย และควรเลือกใช้ FLTK backend
      แต่เป้าหมายของผมคือทำ backend ที่อิงกับ Win32 ซึ่งตอนนี้ยังมีลำดับความสำคัญต่ำ แม้ว่าขั้นแรกจะเสร็จแล้ว: https://github.com/roblillack/spot/pull/4