Spot - ชุดเครื่องมือ GUI เดสก์ท็อปสำหรับภาษา Go ที่คล้าย React
(github.com/roblillack)- 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 ที่ให้มาสองแบบ
- การใช้งานที่อิงกับ FLTK: ใช้ go-fltk
- การใช้งานที่อิงกับ Cocoa: ใช้เวอร์ชันที่ดัดแปลง ของ gocoa
- บน macOS จะใช้ Cocoa backend และบนแพลตฟอร์มอื่นจะใช้ backend ที่อิงกับ FLTK
- บน macOS ก็สามารถเลือกใช้ FLTK ได้เช่นกัน และการปรับปรุงการรองรับ Windows ยังเป็นแผนในอนาคต
spotคือ core package ที่ให้ โมเดลแบบ reactive และความสามารถด้านการเรนเดอร์ และสามารถใช้ร่วมกับชุด control ใดก็ได้ที่ implements อินเทอร์เฟซspot.Controlspot/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 เอง
- เพียงสร้าง struct ที่ implements อินเทอร์เฟซ
- ขณะนี้ยังไม่รองรับการใช้
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
- Button: ปุ่มสำหรับเรียกใช้งาน action แบบง่าย ใช้
- ยังมีคอนโทรลที่ทำเพียงบางส่วนหรือยังไม่เริ่ม
- Dial: คอนโทรลสถานะแบบวงกลม และอยู่ในสถานะ ⚠️ ทำบางส่วน
- ComboBox: เมนูดรอปดาวน์ที่รวมการป้อนข้อความไว้ด้วย และยังไม่ได้เริ่มทำ
- มีการเสนอไลบรารี native Windows control
https://github.com/rodrigocfd/windigoเป็นตัวเลือก backend ที่อาจใช้ในอนาคต
1 ความคิดเห็น
ความคิดเห็นจาก 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
มันเป็นทูลคิตแบบเนทีฟ แต่ผมประทับใจที่ FLTK รองรับการซูมทั้งแอปด้วย Ctrl-+ และ Ctrl+- เหมือนเบราว์เซอร์ และ https://github.com/fltk-rs/fltk-theme?tab=readme-ov-file#wid... ก็ทำให้ผมรู้สึกดีกับระดับที่ทำให้ FLTK ดู “เนทีฟ” ได้
ที่เกี่ยวข้องกัน ผมเพิ่งไปเจอ GoVCL https://z-kit.cc/en/ และอยากลองใช้ดูสักครั้ง
Spot “Hello World” แบบ self-contained มีขนาด 2.3MiB บน Mac ของผม ไม่ได้สวยมาก แต่สำหรับผมใช้งานได้สบาย
สงสัยว่าข้อดีของแนวทาง virtual control tree เมื่อเทียบกับการอัปเดตคอนโทรลที่แสดงต่อผู้ใช้โดยตรงคืออะไร
คุณต้องเขียน 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 เนี่ยนะ?
ถ้ารู้จักสิ่งนี้เมื่อ 3 สัปดาห์ก่อนก็คงดี หรือถ้าดูจากประวัติ commit แล้วมันมีอยู่ตอนนั้นก็คงดีเหมือนกัน ผมพูดมานานแล้วว่าถ้ามี React ที่พอร์ตมาเป็น Go หรือเฟรมเวิร์กแนว React สำหรับ Go ประสบการณ์การพัฒนาจะดีมาก และอันนี้ก็ดูตรงเป๊ะเลย
ก่อนจะไปเข้าใจเรื่องนี้จาก React.lua ผมเคยไม่ชอบ React.js พอสมควร
สุดท้ายผมหาวิธีทำอะไรคล้าย ๆ กันโดยใช้เพียง
html/templateมาตรฐานของ Go และเขียนรายละเอียดไว้ที่นี่: https://www.sheshbabu.com/posts/react-like-composition-using...ปัญหาใหญ่คือพอปล่อยเป็นเดสก์ท็อปแล้วก็มักจะอยากได้ เวอร์ชันเว็บ ด้วย เว้นแต่ว่าจะเป็นแอปเฉพาะทางมาก ๆ ที่ต้องโต้ตอบกับระบบปฏิบัติการอย่างหนัก
หรือไม่ก็ต้องการบางอย่างที่รองรับหลายแพลตฟอร์มรวมถึงมือถือไปเลย
ผมหามาค่อนข้างนานแล้ว แต่ตัวที่ใกล้เคียงที่สุดคือ Qt กับ React Native ซึ่งทั้งคู่ก็เป็นตัวเลือกที่ชวนปวดหัวด้วยหลายเหตุผล
FLTK รองรับ Windows อยู่แล้ว เลยสงสัยว่ายังไม่รองรับ Windows เพราะมีแผนจะใช้ทางแก้อื่นหรือเปล่า
แต่เป้าหมายของผมคือทำ backend ที่อิงกับ Win32 ซึ่งตอนนี้ยังมีลำดับความสำคัญต่ำ แม้ว่าขั้นแรกจะเสร็จแล้ว: https://github.com/roblillack/spot/pull/4