แนะนำ HN: Libmui ไลบรารีวิดเจ็ตสไตล์ macOS คลาสสิกสำหรับ Linux
(github.com/buserror)- Libmui เป็นไลบรารี UI ที่จำลอง API “Toolbox” ของ Macintosh Classic ขึ้นมาจำนวนมาก แม้จะยังไม่ครบถ้วนทั้งหมด แต่มีฟีเจอร์ที่จำเป็นสำหรับ MII Apple //e emulator และแอปพลิเคชันง่าย ๆ
- เริ่มต้นจากการเป็นไลบรารี UI สำหรับ MII โดยมีเป้าหมายเป็น UI ที่จัดวางด้วยมือ แทนการพึ่งพา dependency จำนวนมากหรือเมนูสไตล์เกมแบบ “Arrow Keys + Return + Escape”
- การเรนเดอร์ทำโดยวาดลงบน บัฟเฟอร์ ARGB แล้วคัดลอกไปยัง OpenGL texture หรือ X11/XCB shared pixmap พร้อมติดตาม invalid region เพื่อวาดซ้ำเฉพาะส่วนที่จำเป็น
- API ทำงานแบบ อะซิงโครนัสและอิง callback ต่างจาก Macintosh Toolbox เดิม โดยเมื่อสถานะเปลี่ยน UI จะวาดใหม่เมื่อจำเป็น และส่งอีเวนต์ผ่าน callback แทนการ polling
- มีฟังก์ชัน Window, Menu, Control, List, Alert และ Standard File แต่ยังไม่รองรับ zooming, resizing, Save dialog, dark mode, theme, Wayland, GTK/QT/SDL หรือ binding สำหรับ Rust/Go/Python
สิ่งที่ Libmui สร้างขึ้น
- Libmui เป็นไลบรารีที่จำลอง API “Toolbox” ของ Macintosh Classic มาจำนวนมาก
- แม้จะยังไม่ใช่การทำซ้ำแบบสมบูรณ์ แต่ครอบคลุมส่วนที่จำเป็นสำหรับแอปง่าย ๆ บางตัวและ MII Apple //e emulator
- จุดเริ่มต้นมาจากความต้องการไลบรารี UI สำหรับ MII ที่ไม่มี dependency มากเกินไป และไม่โน้มเอียงไปทาง UI ที่ควบคุมเมนูแบบเกม
- เคยลองใช้ Nuklear immediate mode UI มาก่อน แต่ไม่ชอบหน้าตา มีข้อจำกัดในการปรับแต่ง และมองว่า layout engine จัดวางองค์ประกอบไม่ตรงตำแหน่งที่ต้องการ
- อีกเหตุผลหนึ่งคือประสบการณ์ที่พบว่า immediate mode UI เก็บสถานะภายในด้วย hash ซึ่ง hash collision สามารถก่อปัญหาในการดีบักได้จริง
- เป้าหมายคือ UI ที่ใกล้เคียงกับ UI ที่ขัดเกลาด้วยมือโดยตรง มากกว่า UI ที่ปล่อยให้ layout engine ตัดสินใจเอง
โมเดลการเรนเดอร์และการทำงาน
- Libmui วาด UI ลงบน “screen” ที่เป็น บัฟเฟอร์ ARGB
- ใน MII บัฟเฟอร์นี้จะถูก overlay เป็น OpenGL texture
- เดโม playground ในโฟลเดอร์ example จะคัดลอกไปยังหน้าต่าง X11 ด้วย XCB shared pixmap และทำงานผ่าน remote X11 ได้
- เหมือนระบบปฏิบัติการยุคเก่า คือจะติดตาม invalid region แล้ววาดใหม่เฉพาะส่วนที่จำเป็น
- จึงไม่ต้องวาดทั้งหน้าจอใหม่ทุกครั้ง ทำให้ overdraw ต่ำมาก
- หากต้องวาดลง framebuffer 16 บิต เป็นต้น ก็ต้องแปลงจากเอาต์พุต ARGB โดยตรง
- แต่เพราะแปลงเฉพาะ dirty region ภาระจึงไม่สูงนัก
- แม้จะสามารถเปลี่ยนการเรนเดอร์ให้เป็นแบบเวกเตอร์ เช่น vertex buffer ได้ แต่ผู้พัฒนามองว่าวิธีปัจจุบันเร็วพอแล้ว และไม่จำเป็นต้องย้อนกลับไปใช้แนวทางวาดใหม่ทั้งหน้าจอแบบ immediate mode UI
จุดที่ต่างจาก Macintosh Toolbox ดั้งเดิม
- รูปลักษณ์เริ่มต้นจาก MacOS 8/9 แต่ให้ความรู้สึกเหมือนตัดองค์ประกอบโทน grayscale ออก และเลือกแนวทางของ System 7 เพราะมองว่าหน้าตาแบบ flat ดูเก่าได้ดีกว่า
- popup menu ใกล้เคียงกับ OS8 มากกว่า ส่วน scrollbar ใกล้ GS/OS มากกว่า
- ความต่างสำคัญของ API คือทำงานแบบ อะซิงโครนัสเต็มรูปแบบ
- จึงไม่สามารถวาดลง window หรือ GrafPort ได้ทุกเมื่อด้วย spinloop แบบเดิม
- เมื่อสถานะ UI เปลี่ยน ตัว UI จะวาดใหม่เองเมื่อจำเป็น
- การจัดการอีเวนต์เป็นแบบ อิง callback
- ไม่ต้อง polling เพื่อตรวจว่าเกิดอะไรขึ้นกับ UI
- เมื่อมีการคลิกเมนูไอเท็มหรือกดคีย์ลัด จะมีการเรียก action callback
- โครงสร้างเชิงแนวคิดเรียบง่ายกว่าต้นฉบับ
- ทุกอย่างคือ
mui_windowหรือmui_control - windows, menubars, menus คือ
mui_window - ชื่อเมนู, รายการเมนู, องค์ประกอบทั้งหมดในหน้าต่าง และเส้นคั่น คือ
mui_control
- ทุกอย่างคือ
ตัวจัดการและคอนโทรลที่มีให้
-
Window Manager
- รองรับการสร้างหน้าต่างและการวาดภายในหน้าต่าง
- รองรับสูงสุด 15 layer, clipping, การทำงานแบบ BringToFront และการลากหน้าต่าง
- ระบบพิกัดถูกจำกัดไว้คล้ายของเดิม คือมีแค่ screen coordinates และ window content coordinates
- จัดการรายการ invalid rectangle เพื่อไม่ต้องวาดทั้งหน้าต่างใหม่ทุกครั้ง
- zooming และ resizing ยังเป็น TODO
- ตั้งใจไม่รองรับ transparent windows
- เพราะใช้การวาดหน้าต่างจากบนลงล่างเพื่อเพิ่มประสิทธิภาพของ clipping
- หากต้องจัดการความโปร่งใสจะต้องวาดจากล่างขึ้นบน และจะต้องวาดเนื้อหาซ้ำมากขึ้น
- อย่างไรก็ตาม ยังสามารถทำ alpha blend ทั้งหน้าจอ UI ไปยังตำแหน่งที่ต้องการได้
-
Menu Manager
- รองรับ menubar, menus, checkmarks และ keyboard shortcuts
- ออกแบบให้ดูเหมือน System 7/8 หรือ GS/OS
- มี hierarchical menus แต่ยังไม่เหมือนต้นฉบับทั้งหมดและยังควรปรับปรุง
- การแสดงผลและการเลื่อนของ popup ขนาดใหญ่มากยังเป็น TODO
- รองรับ sticky menus ไปบางส่วนแล้ว แต่ยังทำงานไม่ถูกต้องจึงปิดไว้
-
Control Manager
- รองรับ buttons, checkboxes, radio buttons, vertical scrollbars และ textboxes แบบตัดบรรทัด
- Edit Field ยังอยู่ระหว่างพัฒนา และ Slider ยังไม่มี
- ต้นแบบ text edit control ใช้ได้ดีกับการป้อนข้อความบรรทัดเดียว แต่ยังไม่เหมาะกับกล่องข้อความหลายบรรทัด
-
List Manager
- ตอนนี้ยังถูก hardcode ไว้ใกล้เคียงกับการแสดงชื่อไฟล์
- รองรับ arrow keys, page up/down และ scroll wheel
- เหมือน MacOS ดั้งเดิมตรงที่ค้นหารายการที่ต้องการได้ด้วย typeahead
- ความสามารถในการใช้ font compression หรือ ellipsis abbreviation เมื่อข้อความยาวเกินไปยังเป็น TODO
-
Alerts และ Standard File
- Alert มี dialog ทั่วไปแบบ Cancel + OK
- alert แบบอื่น ๆ ยังเป็น TODO
- Standard File ให้ open file dialog แบบคลาสสิก
- ฟังก์ชันนี้เป็นหนึ่งในเป้าหมายหลักช่วงแรกของไลบรารี
- Save dialog ยังเป็น TODO
- มี popup เพิ่มเติมสำหรับแสดงไดเรกทอรีที่ใช้ล่าสุด
- รองรับ arrow keys, page up/down และการค้นหาไฟล์แบบ typeahead
-
Resource Manager
- ไม่มี Resource Manager
- มองว่าจำเป็นต้องมีเครื่องมืออย่าง ResEdit จึงยังอยู่นอกขอบเขตปัจจุบัน
- มีไอเดียเรื่องฟอร์แมต MessagePack สำหรับ resource แต่เก็บไว้เป็นงานในอนาคต
Dependency และการบิลด์
- dependency ภายนอกมีเพียง libpixman
- libpixman เป็นไลบรารีสำหรับจัดการพิกเซล และมีฟังก์ชัน region ที่มีประโยชน์ต่อ clipping
- แม้จะไม่ดีเท่า region ของ QuickDraw แต่ก็มองว่าเพียงพอ
- ยังมีองค์ประกอบที่รวมมาในซอร์สด้วย
- libcg: renderer แบบ antialiased ขนาดเล็กคล้าย cairo ประกอบด้วย 2 ไฟล์
- stb_truetype.h: ใช้สำหรับโหลดฟอนต์ TrueType
stb_ttc.h: ส่วนขยายของstb_truetype.hสำหรับสร้าง dictionary ของฟอนต์/กริฟ, hash table และ font texture- โค้ด 2D geometry มีอายุมากกว่า 25 ปี และรวมอยู่ใน libc3 ด้วย
- การบิลด์ใช้ Makefile แบบเรียบง่าย โดยรัน
makeจากไดเรกทอรีราก - การบิลด์ tests, demos, samples ต้องใช้
xcb,xcb-shm,xcb-randr,xkbcommon-x11 - หากใช้ Nvidia binary driver จะต้องเพิ่ม
Option "AllowSHMPixmaps" "1"ในDeviceของ/etc/X11/xorg.confเพื่อให้mui_shellทำงานได้
วิธีใช้งานและเวิร์กโฟลว์การพัฒนา
- แนะนำให้เริ่มจากแก้ไข
mui_shell.cและmui_widgets_demo.c ui_mui_shellจะโหลดmui_widgets_demo.soเป็น plugin และ reload อัตโนมัติเมื่อพบการเปลี่ยนแปลง- เมื่อแก้
mui_widgets_demo.cแล้ว ระบบจะ reload และรันใหม่ ทำให้สร้าง dialog ใหม่ได้อย่างรวดเร็ว - หากรัน
make watchในไดเรกทอรีlibmuiจะมีการบิลด์ไลบรารีและmui_shellใหม่โดยอัตโนมัติเมื่อมีการเปลี่ยนแปลง - เมื่อนำไปใช้ร่วมกับ auto save ของตัวแก้ไข ก็จะได้เวิร์กโฟลว์แบบแก้ไขไปพร้อมกับบิลด์และรันต่อเนื่อง
สิ่งที่ไม่มีให้โดยชัดเจน
- ไม่มี dark mode
- ไม่รองรับ theme
- ไม่มี transparent windows และ cube effect
- sticky menus ยังไม่เปิดใช้งานในตอนนี้
- ไม่ใช้
cmake,meson,ninja, autotools - ไม่มี language binding สำหรับ Rust, Go, Python
- ไม่ใช้เฟรมเวิร์กอย่าง GTK, QT
- ไม่ใช้ SDL
- ไม่รองรับ Wayland
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ที่เกี่ยวข้องกัน มีฟอนต์ TrueType แบบ public domain ที่ทำซ้ำ ฟอนต์ระบบ Chicago ต้นฉบับได้ค่อนข้างดี: https://fontlibrary.org/en/font/chicagoflf
Charcoal ที่ใช้หลัง System 8.x เป็นต้นมานั้นเป็นที่รู้จักน้อยกว่ามาก และโดยส่วนตัวผมมองว่าเป็นการปรับปรุงที่ดีขึ้นพอสมควร
ถึงอย่างนั้น การเปลี่ยนเป็น Chicago ในไลบรารีจริง ๆ แล้วค่อนข้างง่าย นอกจากตัวทำซ้ำที่พูดถึงแล้ว ยังมีเวอร์ชัน TTF แบบ “plain” ของ Chicago ดั้งเดิมลอยอยู่ที่ไหนสักแห่งด้วย
จากนั้นใช้เครื่องมือบรรทัดคำสั่งของ FontForge แปลง TTF เป็น OTF ได้: https://www.macintoshrepository.org/1682-mac-os-7-6-x
fontforge -script -c 'Open($1); Generate($2);' input_font.ttf output_font.otfเจ๋งมาก Michel ทำสิ่งนี้ไว้สำหรับ Apple II emulator ของเขา ส่วนผมเอามาใช้แบบกึ่งเล่น ๆ เพื่อแทนที่ฟรอนต์เอนด์ของ Archimedes emulator
ยังอยู่ช่วงต้น ๆ แต่ถ้าผมเข้าใจ API ได้ ก็แปลว่า API มันต้องดีแน่ ๆ :)
ถึงอย่างนั้น ผมก็ยังรักอินเทอร์เฟซ Mac แบบคลาสสิกอยู่ดี
ผมชอบ 2D graphics rasterizer แบบ dual-header ที่โปรเจกต์นี้ใช้: https://github.com/xboot/libcg
น่าทึ่งเสมอที่สามารถสร้างซอฟต์แวร์ทรงพลังได้ด้วย dependency ขั้นต่ำแบบนี้
การสร้าง UI library ที่เรียบร้อยและทรงพลังพอจะเป็นทางเลือกแทน Electron มันยากขนาดนั้นเลยหรือ?
ยอดเยี่ยมมาก เป็น MIT license และเขียนด้วย C! ถ้าเพิ่ม shim สำหรับ AppKit API ก็อาจพอแข่งกับ GNUstep ได้ด้วยซ้ำ
ดูดีเลย อยากให้เปลี่ยน UI ทั้งหมดของ macOS ของผมให้เป็นแบบนั้นได้จัง
เป็นโปรเจกต์ที่ดี ผมเคยชอบ Classic Mac UI แบบเก่ามาก
ตัวอย่างทั้งหมดดูยอดเยี่ยม และพอดูโค้ดเดโมวิดเจ็ตแล้วก็ดูใช้งานง่ายด้วย
เจ๋งจริง ๆ สงสัยว่าต้องใช้ความพยายามแค่ไหนในการอ่านไฟล์ resource (.rsrc) ของ resource fork แล้วสร้าง UI จากมัน
ถ้าทำได้ก็น่าจะใช้ ResEdit ได้ :-)