Compare commits

...

211 Commits
v0.1.0 ... main

Author SHA1 Message Date
34ef447728 Add more notes to canvas/shatter.go
Maybe this should be an issue.
2024-09-12 14:01:40 -04:00
964381eaf4 Replace NASTY ASS map return with iter.Seq in event.Broadcaster 2024-09-12 03:32:03 -04:00
0efaee30cc Apparently go is picky and wierd 2024-09-12 03:29:37 -04:00
45ec1338af You know what, why not. We can have a little iterators as a treat. 2024-09-12 03:26:48 -04:00
6ee6684eea Upgrade to go 1.22
Recently read https://go.dev/blog/loopvar-preview. Nopony should be
using go versions < 1.22
2024-09-12 03:24:06 -04:00
5ae19bd434 Shatter uses slices.Delete properly 2024-09-12 03:06:31 -04:00
1fb7fac874 Change WindowKindToolbar to WindowKindTorn 2024-09-12 03:02:17 -04:00
679ee2c011 Shatter function uses slices package 2024-09-12 00:23:18 -04:00
484ead0f76 More doc comment improvements 2024-09-12 00:21:44 -04:00
018f93cbf9 Add String method to IconSize 2024-09-12 00:02:08 -04:00
8657730c25 Forgot one 2024-09-11 23:56:12 -04:00
9d63e27ab6 Many doc comment fixes 2024-09-11 23:52:36 -04:00
8a3f41c7db The "pressed" tag now references config.ButtonChordInteract 2024-09-11 23:15:10 -04:00
987bb613dd Change the buttons in config to button chords 2024-09-11 22:56:46 -04:00
7bf62c25fe Fix misspelling in config doc comment 2024-09-11 22:42:35 -04:00
763e5db3fc Config now stores common keybinds 2024-09-11 22:41:30 -04:00
dcdb411c0e Modifier keys are now stored using bit flags
Or-ing together constants is cleaner than a struct literal with
booleans
2024-09-11 22:40:19 -04:00
efbdaef390 Add utilities for key/mouse chords to input 2024-09-11 18:44:26 -04:00
8546f11471 Add config package
Closes #6
2024-09-11 01:49:25 -04:00
46f4c50381 Document the window kinds 2024-09-11 01:06:54 -04:00
b88e32fa49 Introduce the concept of window kinds 2024-09-11 00:54:45 -04:00
8f43fa310c Add new child window constructors
Closes #29
2024-09-11 00:21:57 -04:00
20c7e0fdb1 Add FuncCookie
Closes #27
2024-08-20 22:55:31 -04:00
d8a8ad7e0a Re-organize event.go 2024-08-20 22:55:20 -04:00
3feba5f811 event.Cookie is now just io.Closer
Closes #26
2024-08-20 22:50:51 -04:00
75c654d4ae Add error to Window.Close 2024-08-20 22:42:31 -04:00
da38e5411f Add TryClose event to Window
Closes #24
2024-08-20 22:41:41 -04:00
d47b525e42 Add documentation for AttrKind 2024-08-20 21:29:12 -04:00
862e08edf1 Add AttrCursor 2024-08-20 21:25:54 -04:00
47b2231acd Add list of cursor shapes
Progress on #25
2024-08-20 21:22:19 -04:00
b05e1f5d50 Fixed IconListContract being identical to IconListExpand 2024-08-18 16:16:40 -04:00
608a898be3 Fix deadlock in Stop 2024-08-16 17:40:39 -04:00
91a8ae2fa5 Add some new icons
Closes #23
2024-08-16 17:08:28 -04:00
cacfd20a8a Describe IconUnknown 2024-08-16 17:05:11 -04:00
d08fe845fc Add Bounds, InnerBounds to Window
Closes #22
2024-08-16 17:03:24 -04:00
e23a688103 Remove checkbox icons 2024-08-13 12:30:47 -04:00
f4cc47eb16 Fix meaningless panics 2024-08-13 12:17:54 -04:00
43fb3b8feb Remove AttrIcon
- Has no compelling use case (all use cases need the texture size
  which this attribute cannot provide)
- Duplicates functionality
- The rationale for adding it initially was never that strong
2024-08-11 22:23:42 -04:00
a022fa3ad4 Remove the registry mechanism
Nasin has its own registry system that is way more flexible than
what was in this module, that ought to be used instead.
2024-08-11 01:45:51 -04:00
7e3a9759ee AttrIcon has size information 2024-08-10 21:08:09 -04:00
559490e5e8 Remove AttrSet 2024-08-09 23:24:02 -04:00
bc38ea14e1 Add String methods for all types in unit.go 2024-08-07 19:13:17 -04:00
b264e11ea6 Merge style.go and unit.go 2024-08-03 22:09:16 -04:00
a01e5f8716 Change the name of MimeIcon to MimeIconTexture 2024-08-03 22:04:59 -04:00
3de570373f Remove Style as per #21 2024-08-03 22:04:06 -04:00
a1eb53c4db Added functions to get icon textures from the backend 2024-08-03 21:57:10 -04:00
750882eef1 Address icon attribute changes in #21 2024-08-03 21:52:36 -04:00
a98d09d320 golang.org/x/image is no longer a dependency 2024-08-03 21:26:05 -04:00
e6a4b6c70e Change AttrFont to AttrFace as per #21 2024-08-02 19:12:25 -04:00
03fab6fcc0 Remove the font interface and add a Face struct as per #21 2024-08-02 19:08:48 -04:00
6fd236f96c AttrFace is now AttrFont
Closes #19
2024-07-31 00:35:29 -04:00
cf092b4447 Add UnsetAttr
Fully address #20
2024-07-31 00:19:39 -04:00
8403d621a8 AttrKind values are now part of the API 2024-07-30 18:23:37 -04:00
92660ef7de AttrLayout no longer attempts to compare itself 2024-07-25 18:09:10 -04:00
140de5917f Make text wrapping an attribute 2024-07-25 04:01:46 -04:00
3bd9de9110 AttrSet is now just a map 2024-07-25 03:57:55 -04:00
89f49fee71 Removed Tag from BoxQuerier 2024-07-25 02:59:32 -04:00
f561b71c56 Layouts are now attributes 2024-07-24 20:06:20 -04:00
75fd31bb24 Way better event API for objects 2024-07-24 18:37:45 -04:00
5eeb03b113 Replace the catch methods on ContainerBox with SetInputMask 2024-07-24 14:21:39 -04:00
3b3ea6d837 Various attribute improvements 2024-07-24 14:17:31 -04:00
ab4728144f Tags are now used for checking hover, focus, pressed status 2024-07-24 14:15:35 -04:00
1bc85775b8 Add equality checking for attributes 2024-07-21 22:06:10 -04:00
2b2dca77c8 Fix grammar in readme 2024-07-21 22:06:01 -04:00
d6229459c5 Fix ATextureMode 2024-07-21 10:59:35 -04:00
9914cdeb9c Add TextureMode attribute 2024-07-21 02:55:29 -04:00
ba31748b2e Add convenience constructors for attributes 2024-07-21 00:19:16 -04:00
4b56306bd2 Fix spelling of recommended 2024-07-21 00:10:16 -04:00
6859a2b0f7 Fix BoxQuerier interface 2024-07-21 00:00:00 -04:00
469b27bdfb Rename Icons to IconSize 2024-07-20 23:09:32 -04:00
70aaa79b7d Changed Rule contstructor to Ru 2024-07-20 17:12:54 -04:00
e2017f04ff Create constructor for rules 2024-07-20 17:12:31 -04:00
a8c72f7391 Remove the map of named textures from Style 2024-07-20 14:37:06 -04:00
1d4bc03a7d Remove unnecessary String methods 2024-07-20 14:32:59 -04:00
1030bede90 Clean up Box methods a bit 2024-07-20 14:21:41 -04:00
2570fada95 Redid style system with a focus on attributes 2024-07-20 14:18:49 -04:00
8894b98c8b Add BoxQuerier and BoxArranger for use in Layout 2024-07-20 01:23:46 -04:00
1c20acd993 Revise event API for boxes 2024-07-20 01:14:31 -04:00
87494c014a Change Text, Bytes to FromText, FromBytes 2024-07-19 15:26:03 -04:00
f6556aa57f Fix mergedData.Supported 2024-07-19 15:24:44 -04:00
35b52c6a3f Completely redo the data API 2024-07-19 15:24:16 -04:00
109283f520 Dont't crash if tomo.Run callback calls tomo.Stop 2024-07-19 13:40:56 -04:00
dc50e7290d Remove Window.Widget 2024-06-11 23:41:06 -04:00
e102c032cb Layout recommended size takes in layout hints and boxes 2024-06-07 23:07:15 -04:00
d35b0532a1 Speaks english for 21 years and still has no idea how to spell 2024-06-07 19:22:13 -04:00
dffe766328 Rename theme.go to style.go 2024-06-07 19:19:35 -04:00
c1d77028b4 Add a note that reccomended sizes arent always respected 2024-06-07 19:15:33 -04:00
83b2c3c665 Add system for reccomending widths/heights
Remedy #6
2024-06-07 19:13:22 -04:00
89307e0dd6 Slight wording fix 2024-06-07 19:06:18 -04:00
5d9c584743 Document that windows are supposed to be transparent
Remedy #16
2024-06-07 19:05:38 -04:00
fd7d2b7ead Add Window.SetBounds
Remedy #17
2024-06-07 18:59:43 -04:00
ca4d3e62c4 Remedy #10 2024-06-07 18:57:16 -04:00
d587e39506 Separate icon functionality out of Style and into Icons 2024-06-07 18:27:26 -04:00
f0c092be8a Break some scalars, vectors, and enums into a separate file 2024-06-07 18:21:15 -04:00
06f45c80e2 Change Theme to Style 2024-06-07 18:19:43 -04:00
71171d762c Add a "SetResizable" behavior to Window 2024-06-07 14:11:36 -04:00
341eea5b5d Remove references to MainWindow 2024-06-07 00:56:29 -04:00
453953bbcd Unify Window and MainWindow 2024-06-07 00:54:14 -04:00
31ce286bc7 Window icons are set using textures 2024-06-06 20:15:00 -04:00
26cf1f4a88 Roles are stored in Boxes now 2024-06-03 19:57:20 -04:00
6ef1f7ba78 Add OnSizeChange to SurfaceBox
Remedy #15
2024-06-03 19:37:34 -04:00
d7093be696 Update go.sum 2024-06-03 19:33:07 -04:00
64bf8f3b2d NewCanvas actually returns a Canvas 2024-06-03 19:32:47 -04:00
50cbac17a9 CanvasCloser is now actually a valid type 2024-06-03 19:32:06 -04:00
d096d40c57 Add a way for applications to request canvases
Makes fix to #14 useful
2024-06-03 19:29:41 -04:00
34358da6eb Add canvas.CanvasCloser
Remedy #14
2024-06-03 19:28:03 -04:00
be1b9d77e8 Instruct themes to return nil icons if the icon doesn't exist 2024-05-28 22:01:56 -04:00
d71ac02748 Add unchecked checkbox icon 2024-05-27 15:10:11 -04:00
2bbf85ebd8 Add more missing icons 2024-05-27 04:02:22 -04:00
320535e7f3 Remove Box.Visible, box.SetVisible
It was a bad idea. Remedy #12.
2024-05-26 17:42:11 -04:00
775fb84e9b Add back some missing icons 2024-05-26 17:42:04 -04:00
6bf3450799 Add link to XDG icon specification in icon.go 2024-05-25 00:57:26 -04:00
874171c9de Un-export backend Initialize() function 2024-05-25 00:52:25 -04:00
6cfb16f9fe Change theme variable from current -> theme 2024-05-25 00:51:34 -04:00
73e9b28eeb Redo standard icon set around the XDG icon naming spec
https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
Remedy #11
2024-05-25 00:50:15 -04:00
17039059ce Merge theme package into central package 2024-05-24 21:45:16 -04:00
a6b115b591 Replace SetTexture with SetTextureTile and SetTextureCenter
Remedy #8
2024-05-24 14:47:43 -04:00
7c8b419c65 Elaborate on the difference between Object and Box 2024-05-20 00:30:28 -04:00
ca512af1a5 Add ContentObject, remedy #7 2024-05-20 00:25:41 -04:00
b4f8d11817 Don't downplay the usefulness of ContentBox 2024-05-20 00:23:24 -04:00
9c3ae7afa5 Add API for using platform dependent graphics contexts 2024-05-20 00:18:49 -04:00
a18628c26d Changed the wording of the coment describing canvas.Drawer 2024-05-20 00:18:26 -04:00
a9fc52c55b Change clip behaviors to sub behaviors
Remedies #4
2024-05-15 01:37:21 -04:00
e745e80f09 Add more information to README.md 2024-05-14 12:43:40 -04:00
748996c789 Be more descriptive with what a path is 2024-05-14 12:34:42 -04:00
d4aac7e26c Set CapRound as the default cap style 2024-05-14 12:32:44 -04:00
3d645b8064 Tweak doc comments for canvas.Pen 2024-05-14 12:32:09 -04:00
bde1a2bc34 Remove wierd spacing from box interface definitions
Seemed like a good idea at the time
2024-05-14 12:20:44 -04:00
d43ba6b1af Replace Window's Show/Hide methods with Visible/SetVisible 2024-05-14 12:18:21 -04:00
096c1b5e1b Add Visible() and SetVisible() to Box 2024-05-14 12:17:14 -04:00
c44ff4a34a Change ContainerBox.Delete() to Remove()
Remedy #3
2024-05-14 12:11:00 -04:00
dc02f4dfb4 Remedy #1 2024-05-14 12:09:34 -04:00
7c30b2bac0 Add requirement for ContainerBox.Add to remove a previously
parented object first
2024-05-14 12:03:10 -04:00
24264bbc91 Fix capitalization errors with previous commit 2024-05-14 11:58:48 -04:00
f167af3281 Describe the canvas used by CanvasBox in detail 2024-05-14 11:57:48 -04:00
9a5b4ee7e8 Add home icon 2024-05-05 02:53:07 -04:00
3e561d7bf0 Fix wording of theme.Theme doc comment 2024-05-03 13:46:50 -04:00
b96e8f744f Namespace icon IDs 2024-05-03 13:21:35 -04:00
9aa6f2900e Added application role icons 2024-05-03 13:10:44 -04:00
a3fc0ce66d Remove ApplicationIcon from Theme
Since Icon can now take in arbitrary strings, we can just have it
serve application icons as well.
2024-05-03 13:08:14 -04:00
65bf341514 Icon IDs are now strings 2024-05-03 13:07:27 -04:00
042f2f0131 Completely remove plugin system
Plugins may be moved to Nasin
2024-04-30 13:12:34 -04:00
4fd5e54e42 Add editorconfig 2024-04-29 16:23:46 -04:00
9a9e546b37 Remove application framework stuff 2024-04-29 00:39:17 -04:00
a2a5c16e38 Changed the semantics of ContentBounds 2023-09-14 14:47:33 -04:00
28cd889254 Removed tiler for now, needs to be rethought a bit 2023-09-08 20:57:15 -04:00
e682fdd9d8 Add icon for switch 2023-09-08 20:57:00 -04:00
9719391e5d NewApplicationWindow returns MainWindow nows 2023-09-08 16:29:03 -04:00
8a531986eb What??? 2023-09-07 18:25:35 -04:00
c3c6ff61f5 Add Tiler interface 2023-09-05 18:14:36 -04:00
89f7bf47ce Add ability to create an undecorated window 2023-09-05 13:21:59 -04:00
bebd58dac1 Added event capturing to containers 2023-09-05 13:10:35 -04:00
f9a85fd949 Added filesystems for application data 2023-09-04 13:48:03 -04:00
6ac653adb6 Made ownership of textures more explicit 2023-09-04 12:21:17 -04:00
7b28419432 Texture must now implement Image 2023-09-04 12:07:29 -04:00
57e6a9ff21 lolll whoops 2023-09-04 02:56:00 -04:00
a06f94e41b Added support for defining applications as objects 2023-09-04 02:26:21 -04:00
4d157756eb Added a String method to a bunch of stuff 2023-09-04 01:47:03 -04:00
63a67e40d1 Added a Bounds() method to Texture 2023-09-04 01:28:04 -04:00
b629b4eb4e Cleared up wording around theme textures 2023-08-29 15:52:07 -04:00
7510047ef3 Fix package name errors in theme 2023-08-24 16:30:10 -04:00
fdea479ee7 Textures have been moved to the canvas module 2023-08-24 01:01:40 -04:00
dc31de0ecb Textures are now actually used 2023-08-23 18:04:54 -04:00
a8d5a64837 Added SetTexture on Object 2023-08-21 21:52:51 -04:00
50697e4369 All icons now return textures 2023-08-20 18:41:46 -04:00
3614979e35 Add a function to protect textures 2023-08-20 18:33:20 -04:00
5f5b928528 Icons now use textures 2023-08-20 18:28:30 -04:00
b62b846745 AhhhahsdjashdMerge branch 'main' of git.tebibyte.media:tomo/tomo 2023-08-20 17:58:45 -04:00
35c6e167be Added a texture interface 2023-08-20 17:54:06 -04:00
6ced7d1372 Fixed syntax errors 2023-08-12 00:56:13 -04:00
e259f122c7 Added an icon API 2023-08-12 00:51:16 -04:00
8e25397a05 Theme roles now have a variant field 2023-08-10 17:49:22 -04:00
2884604fdd Renamed Object.Box to GetBox to resolve naming conflicts 2023-08-10 17:48:40 -04:00
f99d9e0d2a Upgrade x/image 2023-08-09 20:55:46 -04:00
c785fb461c Colors now operate more sensibly 2023-08-09 15:13:19 -04:00
487471d7a9 Add colors 2023-08-09 12:08:17 -04:00
2f5421a5c9 Add Role constructor 2023-08-08 03:00:41 -04:00
d0f7047fcf Fix package import error in theme 2023-08-07 21:57:50 -04:00
Sasha Koshka
e63ebdb89e Add MultiCookie to make theming easier 2023-08-07 21:56:28 -04:00
Sasha Koshka
9d40ab654a Clarfied wording in Role.Object 2023-08-07 21:51:05 -04:00
Sasha Koshka
522ff64fd3 Added a theme package 2023-08-07 21:49:11 -04:00
Sasha Koshka
e14bd81c04 Add SetDotColor method 2023-08-07 21:09:58 -04:00
dc377c36a5 Add function keys up to F24 2023-08-02 01:37:46 -04:00
2b99a98a8e Add a ton more doc comments 2023-08-02 01:34:07 -04:00
d1b62f5560 Add dot manipulation to TextBox 2023-07-20 00:14:15 -04:00
85fe5ac65b Added text manipulation 2023-07-19 23:58:27 -04:00
14fc0ba372 Add more doc comments 2023-07-18 21:49:36 -04:00
9f4e8a539a The Do function is now thread safe 2023-07-16 01:06:24 -04:00
1cb3be8de8 Added a global Do function 2023-07-16 00:33:44 -04:00
32e58ce63d Container event propagation can be disabled 2023-07-13 12:53:08 -04:00
4dbd86cec3 ContainerBox can now be aligned as well 2023-07-13 03:00:50 -04:00
573212fe7d Add support for text wrapping 2023-07-12 18:56:04 -04:00
ae5c177484 Minimum sizes make more sense now 2023-07-12 01:45:08 -04:00
facd85ef21 Add SetText method to TextBox 2023-07-08 23:49:51 -04:00
ec967fe4f8 Better way of representing layout hints 2023-07-05 17:44:08 -04:00
9efeaef8a8 Revised layout interface 2023-07-05 04:21:17 -04:00
d6baf82a94 Removed the Gap type 2023-07-05 04:20:56 -04:00
Sasha Koshka
0824aca4ba Added shatter algorithm to canvas 2023-07-05 03:14:34 -04:00
ec7596fa2d FuncBroadcaster actually broadcasts lol 2023-07-05 00:45:32 -04:00
66e1caeebf Reorganized the box interfaces somewhat 2023-07-02 02:46:12 -04:00
8b78d16506 Add window behavior to box 2023-07-02 00:31:05 -04:00
ac1fe7a75a Add methods for working with insets 2023-07-01 19:23:33 -04:00
78aa13f388 Embed Broadcaster in FuncBroadcaster 2023-07-01 19:08:39 -04:00
Sasha Koshka
3e92de7485 Make broadcaster generic 2023-07-01 19:00:26 -04:00
b00a7302b4 Return error from NewWindow 2023-07-01 11:45:48 -04:00
48fe7ef5e1 Added a rectangle method to Canvas 2023-07-01 03:02:08 -04:00
bb07363c89 Window now has SetTitle method 2023-06-30 22:08:17 -04:00
41d3cd8729 Backend.NewWindow now can return error 2023-06-30 22:07:58 -04:00
c7c6fd2894 Add package-level doc comments 2023-06-30 19:40:45 -04:00
a5ba4cd855 Load plugins on start 2023-06-30 19:30:17 -04:00
54a601a149 Add license 2023-06-30 17:45:29 -04:00
22 changed files with 2806 additions and 353 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 8
charset = utf-8
[*.md]
indent_style = space
indent_size = 2

674
LICENSE Normal file
View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -1,6 +1,8 @@
# tomo
WIP rewrite of tomo.
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/tomo/tomo.svg)](https://pkg.go.dev/git.tebibyte.media/tomo/tomo)
This module will serve as a wafer-thin collection of interfaces and glue code so
that plugins will be an actual viable concept.
Tomo is a lightweight GUI toolkit written in pure Go. This repository defines
the API that other components of the toolkit agree upon. In order to use Tomo in
an application, use [Nasin](https://git.tebibyte.media/tomo/nasin), which builds
an application framework on top of Tomo.

282
attribute.go Normal file
View File

@ -0,0 +1,282 @@
package tomo
import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo/canvas"
// Attr modifies one thing about a Box's style.
type Attr interface {
// Equals returns true if both Attrs can reasonably be declared equal.
Equals (Attr) bool
Kind () AttrKind
attr ()
}
// AttrKind enumerates all attribute kinds. Each Attr- type has one of these.
type AttrKind int; const (
AttrKindColor AttrKind = iota
AttrKindTexture
AttrKindTextureMode
AttrKindBorder
AttrKindMinimumSize
AttrKindPadding
AttrKindGap
AttrKindTextColor
AttrKindDotColor
AttrKindFace
AttrKindWrap
AttrKindAlign
AttrKindOverflow
AttrKindLayout
AttrKindCursor
)
// AttrColor sets the background color of a Box.
type AttrColor struct { color.Color }
// AttrTexture sets the texture of a Box.
type AttrTexture struct { canvas.Texture }
// AttrTextureMode sets the rendering mode of a Box's texture.
type AttrTextureMode TextureMode
// AttrBorder sets the Border of a Box.
type AttrBorder []Border
// AttrMinimumSize sets the minimum size of a Box.
type AttrMinimumSize image.Point
// AttrPadding sets the inner padding of a Box.
type AttrPadding Inset
// AttrGap sets the gap between child Boxes, if the Box is a ContainerBox.
type AttrGap image.Point
// AttrTextColor sets the text color, if the Box is a TextBox.
type AttrTextColor struct { color.Color }
// AttrDotColor sets the text selection color, if the Box is a TextBox.
type AttrDotColor struct { color.Color }
// AttrFace sets the type face, if the Box is a TextBox.
type AttrFace Face
// AttrWrap sets whether or not the text wraps, if the Box is a TextBox.
type AttrWrap bool
// AttrAlign sets the layout alignment, if the Box is a ContentBox.
type AttrAlign struct { X, Y Align }
// AttrOverflow sets the overflow, if the Box is a ContentBox.
type AttrOverflow struct { X, Y bool }
// AttrLayout sets the Layout, if the Box is a ContentBox.
type AttrLayout struct { Layout }
// AttrCursor sets the mouse cursor shape.
type AttrCursor Cursor
// AColor is a convenience constructor for AttrColor.
func AColor (col color.Color) AttrColor {
return AttrColor { Color: col }
}
// ATexture is a convenience constructor for AttrTexture.
func ATexture (texture canvas.Texture) AttrTexture {
return AttrTexture { Texture: texture }
}
// ATextureMode is a convenience constructor for AttrTextureMode.
func ATextureMode (mode TextureMode) AttrTextureMode {
return AttrTextureMode(mode)
}
// ABorder is a convenience constructor for AttrBorder.
func ABorder (borders ...Border) AttrBorder {
return AttrBorder(borders)
}
// AMinimumSize is a convenience constructor for AttrMinimumSize.
func AMinimumSize (x, y int) AttrMinimumSize {
return AttrMinimumSize(image.Pt(x, y))
}
// APadding is a convenience constructor for AttrPadding.
func APadding (sides ...int) AttrPadding {
return AttrPadding(I(sides...))
}
// AGap is a convenience constructor for AttrGap.
func AGap (x, y int) AttrGap {
return AttrGap(image.Pt(x, y))
}
// ATextColor is a convenience constructor for AttrTextColor.
func ATextColor (col color.Color) AttrTextColor {
return AttrTextColor { Color: col }
}
// ADotColor is a convenience constructor for AttrDotColor.
func ADotColor (col color.Color) AttrDotColor {
return AttrDotColor { Color: col }
}
// AFace is a convenience constructor for AttrFace.
func AFace (face Face) AttrFace {
return AttrFace(face)
}
// AWrap is a convenience constructor for AttrWrap.
func AWrap (wrap bool) AttrWrap {
return AttrWrap(wrap)
}
// AAlign is a convenience constructor for AttrAlign.
func AAlign (x, y Align) AttrAlign {
return AttrAlign { X: x, Y: y }
}
// AOverflow is a convenience constructor for AttrOverflow.
func AOverflow (x, y bool) AttrOverflow {
return AttrOverflow { X: x, Y: y }
}
// ALayout is a convenience constructor for AttrLayout.
func ALayout (layout Layout) AttrLayout {
return AttrLayout { Layout: layout }
}
// ACursor is a convenience constructor for AttrCursor.
func ACursor (cursor Cursor) AttrCursor {
return AttrCursor(cursor)
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrColor) Equals (other Attr) bool {
if other, ok := other.(AttrColor); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrTexture) Equals (other Attr) bool {
if other, ok := other.(AttrTexture); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrTextureMode) Equals (other Attr) bool {
if other, ok := other.(AttrTextureMode); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrBorder) Equals (other Attr) bool {
if other, ok := other.(AttrBorder); ok {
if len(this) != len(other) { return false }
for index := range this {
thisBorder := this[index]
otherBorder := other[index]
if thisBorder != otherBorder { return false }
}
return true
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrMinimumSize) Equals (other Attr) bool {
if other, ok := other.(AttrMinimumSize); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrPadding) Equals (other Attr) bool {
if other, ok := other.(AttrPadding); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrGap) Equals (other Attr) bool {
if other, ok := other.(AttrGap); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrTextColor) Equals (other Attr) bool {
if other, ok := other.(AttrTextColor); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrDotColor) Equals (other Attr) bool {
if other, ok := other.(AttrDotColor); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrFace) Equals (other Attr) bool {
if other, ok := other.(AttrFace); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrWrap) Equals (other Attr) bool {
if other, ok := other.(AttrWrap); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrAlign) Equals (other Attr) bool {
if other, ok := other.(AttrAlign); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrOverflow) Equals (other Attr) bool {
if other, ok := other.(AttrOverflow); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrLayout) Equals (other Attr) bool {
// for some goofy reason if we try to compare two AttrLayouts we get a
// fucking runtime error????? its probably for the best anyways because
// two layouts cannot "reasonably" be declared equal
return false
}
// Equals returns true if both Attrs can reasonably be declared equal.
func (this AttrCursor) Equals (other Attr) bool {
if other, ok := other.(AttrCursor); ok {
return this == other
} else {
return false
}
}
func (AttrColor) Kind () AttrKind { return AttrKindColor }
func (AttrTexture) Kind () AttrKind { return AttrKindTexture }
func (AttrTextureMode) Kind () AttrKind { return AttrKindTextureMode }
func (AttrBorder) Kind () AttrKind { return AttrKindBorder }
func (AttrMinimumSize) Kind () AttrKind { return AttrKindMinimumSize }
func (AttrPadding) Kind () AttrKind { return AttrKindPadding }
func (AttrGap) Kind () AttrKind { return AttrKindGap }
func (AttrTextColor) Kind () AttrKind { return AttrKindTextColor }
func (AttrDotColor) Kind () AttrKind { return AttrKindDotColor }
func (AttrFace) Kind () AttrKind { return AttrKindFace }
func (AttrWrap) Kind () AttrKind { return AttrKindWrap }
func (AttrAlign) Kind () AttrKind { return AttrKindAlign }
func (AttrOverflow) Kind () AttrKind { return AttrKindOverflow }
func (AttrLayout) Kind () AttrKind { return AttrKindLayout }
func (AttrCursor) Kind () AttrKind { return AttrKindCursor }
func (AttrColor) attr () { }
func (AttrTexture) attr () { }
func (AttrTextureMode) attr () { }
func (AttrBorder) attr () { }
func (AttrMinimumSize) attr () { }
func (AttrPadding) attr () { }
func (AttrGap) attr () { }
func (AttrTextColor) attr () { }
func (AttrDotColor) attr () { }
func (AttrFace) attr () { }
func (AttrWrap) attr () { }
func (AttrAlign) attr () { }
func (AttrOverflow) attr () { }
func (AttrLayout) attr () { }
func (AttrCursor) attr () { }

View File

@ -1,73 +1,79 @@
package tomo
import "sort"
import "sync"
import "image"
import "errors"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
// Backend is any Tomo implementation. Backends handle window creation, layout,
// rendering, and events so that there can be as many platform-specific
// optimizations as possible.
type Backend interface {
NewWindow (image.Rectangle) MainWindow
// These methods create new Objects. The backend must reject any Object
// that was not made by it.
NewBox () Box
NewTextBox () TextBox
NewCanvasBox () CanvasBox
NewSurfaceBox () (SurfaceBox, error)
NewContainerBox () ContainerBox
// Stop must unblock run.
Run () error
// NewWindow creates a normal Window and returns it.
NewWindow (WindowKind, image.Rectangle) (Window, error)
// NewTexture creates a new canvs.Texture from an image. The backend
// must reject any texture that was not made by it.
NewTexture (image.Image) canvas.TextureCloser
// NewCanvas creates a new canvas.Canvas with the specified bounds. The
// backend must reject any canvas that was not made by it.
NewCanvas (image.Rectangle) canvas.CanvasCloser
// ColorRGBA returns the RGBA of a color according to the current style,
// as specified in color.Color.RGBA. It may be rendered invalid if the
// visual style changes, but the Backend must send a StyleChange event
// to all managed boxes when this happens.
ColorRGBA (id Color) (r, g, b, a uint32)
// IconTexture returns the canvas.Texture of an Icon. It may be closed
// and therefore rendered invalid if the icon set changes, but the
// Backend must send an IconSetChange event to all managed boxes when
// this happens.
IconTexture (Icon, IconSize) canvas.Texture
// MimeIconTexture returns the canvas.Texture of an icon corresponding
// to the specified MIME type. It may be closed and therefore rendered
// invalid if the icon set changes, but the Backend must send an
// IconSetChange event to all managed boxes when this happens.
MimeIconTexture (data.Mime, IconSize) canvas.Texture
// Run runs the event loop until Stop is called, or the Backend
// experiences a fatal error.
Run () error
// Stop must unblock Run. This behavior may only be called from within
// tomo.Stop.
Stop ()
// Do performs a callback function in the main thread as soon as
// Do performs a callback function in the event loop thread as soon as
// possible. This method must be safe to call concurrently.
Do (func ())
}
// Factory is a function that attempts to instatiate a backend. If the
// instantiation process fails at any point, it must clean up all resources and
// return nil and an error explaining what happened.
type Factory func () (Backend, error)
var backendLock sync.Mutex
var backend Backend
var registered []factoryEntry
type factoryEntry struct {
Factory
priority int
func assertBackend () {
if backend == nil { panic("nil backend") }
}
// Register registers a backend factory with the given priority number.
func Register (priority int, factory Factory) {
registered = append(registered, factoryEntry {
priority: priority,
Factory: factory,
})
}
// SetBackend sets the Backend that functions in this package will call upon.
// This function will panic if there is already a Backend running.
func SetBackend (back Backend) {
backendLock.Lock()
defer backendLock.Unlock()
// Initialize instantiates a backend. The first backend (sorted by priority)
// that does not throw an error when initialized is used. If no backend could be
// instantiated, this function returns an error. This function should be called
// only once.
func Initialize () (Backend, error) {
backend, err := instantiate()
if err != nil { return nil, err }
return backend, err
}
func instantiate () (Backend, error) {
if len(registered) < 0 {
return nil, errors.New("no available backends")
if backend != nil {
panic("SetBackend called while another backend was running")
}
// sort backends by priority
sort.Slice(registered, func (left, right int) bool {
return registered[left].priority < registered[right].priority
})
// attempt to instantiate
errorLog := ""
for _, factory := range registered {
backend, err := factory.Factory()
if err == nil {
return backend, nil
} else {
errorLog += " " + err.Error() + "\n"
}
}
return nil, errors.New("all backends failed:\n" + errorLog)
backend = back
}

View File

@ -1,66 +1,97 @@
// Canvas defines a standard interface for images that support drawing
// primitives.
package canvas
import "io"
import "image"
import "image/draw"
import "image/color"
// Cap represents a stroke cap type.
type Cap int; const (
CapButt Cap = iota
CapRound
CapSquare
CapRound Cap = iota // Round cap that surrounds the point
CapSquare // Square cap that surrounds the point
CapButt // Square cap that ends at the point
)
// Joint represents a stroke joint type.
type Joint int; const (
JointRount Joint = iota
JointSharp
JointMiter
JointRount Joint = iota // Rounded joint
JointSharp // Sharp joint
JointMiter // Clipped/beveled joint
)
// StrokeAlign determines whether a stroke is drawn inside, outside, or on a
// path.
type StrokeAlign int; const (
StrokeAlignCenter StrokeAlign = iota
StrokeAlignInner
StrokeAlignOuter
StrokeAlignCenter StrokeAlign = iota // Centered on the path
StrokeAlignInner // Inset into the path
StrokeAlignOuter // Outset around the path
)
// Pen represents a drawing context that is linked to a canvas. Each canvas can
// have multiple pens associated with it, each maintaining their own drawing
// context.
// state.
type Pen interface {
// Draw draws a path
Draw (points ...image.Point)
// Rectangle draws an image.Rectangle.
Rectangle (image.Rectangle)
Closed (bool) // if the path is closed
Cap (Cap) // line cap stype
Joint (Joint) // line joint style
StrokeWeight (int) // how thick the stroke is
StrokeAlign (StrokeAlign) // where the stroke is drawn
// Path draws a path, which is a series of connected points.
Path (points ...image.Point)
// set the stroke/fill to a solid color
// StrokeWeight sets how thick the stroke is. The default value is zero.
StrokeWeight (int)
// SetClosed sets whether the path is a closed shape, or has an open
// side. This only applies if the stroke weight is greater than zero.
Closed (bool)
// Cap sets how the ends of the stroke look. This only applies if the
// stroke weight is greater than zero, and if the path is not closed.
// The default value is CapRound.
Cap (Cap)
// Joint sets how bend points in the stroke look. This only applies if
// the stroke weight is greater than zero. The default value is
// JointRound.
Joint (Joint)
// StrokeAlign sets where the stroke is drawn in relation to the path.
// This only applies if the stroke weight is greater than zero. The
// default value is StrokeAlignCenter.
StrokeAlign (StrokeAlign)
// Stroke sets the stroke to a solid color.
Stroke (color.Color)
Fill (color.Color)
// Fill sets the fill to a solid color.
Fill (color.Color)
// Texture overlaps a texture onto the fill color.
Texture (Texture)
}
// Canvas is an image that supports drawing paths.
type Canvas interface {
draw.Image
// Pen returns a new pen for this canvas.
// Pen returns a new pen for this Canvas.
Pen () Pen
// Clip returns a new canvas that points to a specific area of this one.
Clip (image.Rectangle) Canvas
// SubCanvas returns a returns a Canvas representing the portion of this
// Canvas visible and drawable through a rectangle. The returned value
// shares pixels with the original Canvas.
SubCanvas (image.Rectangle) Canvas
}
// Drawer is an object that can draw to a canvas.
// CanvasCloser is a canvas that can be closed. Anything that receives a
// CanvasCloser must close it after use.
type CanvasCloser interface {
Canvas
io.Closer
}
// Drawer can draw to a canvas.
type Drawer interface {
// Draw draws to the given Canvas.
Draw (Canvas)
}
// PushCanvas is a canvas that can push a region of itself to the screen (or
// PushCanvas is a Canvas that can push a region of itself to the screen (or
// some other destination).
type PushCanvas interface {
Canvas

97
canvas/shatter.go Normal file
View File

@ -0,0 +1,97 @@
package canvas
import "image"
import "slices"
// TODO look into other options besides returning a slice, as that causes memory
// allocations. Perhaps an iterator? Maybe we could also *take in* an iterator
// for the rocks. This could result in zero allocations.
// Shatter takes in a bounding rectangle, and several image.Rectangles to be
// subtracted from it. It returns a slice of rectangles that tile together to
// make up the difference between them. This is intended to be used for figuring
// out which areas of a container box's background are covered by other boxes so
// it doesn't waste CPU cycles drawing to those areas.
func Shatter (
glass image.Rectangle,
rocks ...image.Rectangle,
) (
tiles []image.Rectangle,
) {
// in this function, the metaphor of throwing several rocks at a sheet
// of glass is used to illustrate the concept.
tiles = []image.Rectangle { glass }
for _, rock := range rocks {
// check each tile to see if the rock has collided with it
tileLen := len(tiles)
for tileIndex := 0; tileIndex < tileLen; tileIndex ++ {
tile := tiles[tileIndex]
if !rock.Overlaps(tile) { continue }
newTiles, n := shatterOnce(tile, rock)
if n > 0 {
// the tile was shattered into one or more sub
// tiles
tiles[tileIndex] = newTiles[0]
tiles = append(tiles, newTiles[1:n]...)
} else {
// the tile was entirely obscured by the rock
// and must be wholly removed
tiles = slices.Delete(tiles, tileIndex, tileIndex + 1)
tileIndex --
tileLen --
}
}
}
return
}
func shatterOnce (glass, rock image.Rectangle) (tiles [4]image.Rectangle, n int) {
rock = rock.Intersect(glass)
// |'''''''''''|
// | |
// |###|'''| |
// |###|___| |
// | |
// |___________|
if rock.Min.X > glass.Min.X { tiles[n] = image.Rect (
glass.Min.X, rock.Min.Y,
rock.Min.X, rock.Max.Y,
); n ++ }
// |'''''''''''|
// | |
// | |'''|###|
// | |___|###|
// | |
// |___________|
if rock.Max.X < glass.Max.X { tiles[n] = image.Rect (
rock.Max.X, rock.Min.Y,
glass.Max.X, rock.Max.Y,
); n ++ }
// |###########|
// |###########|
// | |'''| |
// | |___| |
// | |
// |___________|
if rock.Min.Y > glass.Min.Y { tiles[n] = image.Rect (
glass.Min.X, glass.Min.Y,
glass.Max.X, rock.Min.Y,
); n ++ }
// |'''''''''''|
// | |
// | |'''| |
// | |___| |
// |###########|
// |###########|
if rock.Max.Y < glass.Max.Y { tiles[n] = image.Rect (
glass.Min.X, rock.Max.Y,
glass.Max.X, glass.Max.Y,
); n ++ }
return
}

21
canvas/texture.go Normal file
View File

@ -0,0 +1,21 @@
package canvas
import "io"
import "image"
// Texture is a handle that points to a 2D raster image.
type Texture interface {
image.Image
// SubTexture returns a returns a Texture representing the portion of
// this Texture visible through a rectangle. The returned value shares
// pixels with the original Texture.
SubTexture (image.Rectangle) Texture
}
// TextureCloser is a Texture that can be closed. Anything that receives a
// TextureCloser must close it after use.
type TextureCloser interface {
Texture
io.Closer
}

44
config/config.go Normal file
View File

@ -0,0 +1,44 @@
// Package config stores common configuration parameters. They are unmanaged,
// and do not broadcast events when changed. Thus, they must be queried each
// time they are used, whether that be by backends, applications, or objects.
// They are intended to be set by things like application frameworks. Values set
// by other things are subject to being overridden.
package config
import "time"
import "git.tebibyte.media/tomo/tomo/input"
// DoubleClickDelay is the maximum amount of time that can pass between two
// consecutive clicks for them to be considered a double-click.
var DoubleClickDelay time.Duration = time.Second
// ScrollSpeed is how many units (pixels at a scale of 1.0) the content of a
// ContentBox should be moved in response to a scroll delta of 1.0.
var ScrollSpeed int = 16
// KeyChordConfirm is used to activate focused objects, and commonly to signal
// to the application that the user wishes to submit a value they have entered
// using the keyboard into an input field.
var KeyChordConfirm = input.KC(input.KeyEnter, input.ModNone)
// KeyChordClose is used to break out of some mode or state, such as a modal
// dialog.
var KeyChordClose = input.KC(input.KeyEscape, input.ModNone)
// KeyChordFocusNext is used to advance the input focus to the next focusable
// object.
var KeyChordFocusNext = input.KC(input.KeyTab, input.ModNone)
// KeyChordFocusPrevious is used to advance the input focus to the previous
// focusable object.
var KeyChordFocusPrevious = input.KC(input.KeyTab, input.ModShift)
// ButtonChordInteract is used to select, activate, and drag objects.
var ButtonChordInteract = input.BC(input.ButtonLeft, input.ModNone)
// ButtonChordContextMenu is used to open a context menu on an object.
var ButtonChordContextMenu = input.BC(input.ButtonRight, input.ModNone)
// ButtonChordPan is used to move the content of a content object relative to
// its inner bounds.
var ButtonChordPan = input.BC(input.ButtonMiddle, input.ModNone)

35
cursor.go Normal file
View File

@ -0,0 +1,35 @@
package tomo
// Cursor represents a mouse cursor shape.
type Cursor string
// A list of standard cursor shapes. This is based off of the XDG Cursor
// Conventions Specification
// (https://www.freedesktop.org/wiki/Specifications/cursor-spec/).
const (
CursorUnknown Cursor = ""
CursorDefault Cursor = "Default"
CursorText Cursor = "Text"
CursorPointer Cursor = "Pointer"
CursorHelp Cursor = "Help"
CursorProgress Cursor = "Progress"
CursorWait Cursor = "Wait"
CursorCopy Cursor = "Copy"
CursorAlias Cursor = "Alias"
CursorNoDrop Cursor = "NoDrop"
CursorNotAllowed Cursor = "NotAllowed"
CursorAllScroll Cursor = "AllScroll"
CursorRowResize Cursor = "RowResize"
CursorColResize Cursor = "ColResize"
CursorEResize Cursor = "EResize"
CursorNEResize Cursor = "NEResize"
CursorNWResize Cursor = "NWResize"
CursorNResize Cursor = "NResize"
CursorSEResize Cursor = "SEResize"
CursorSWResize Cursor = "SWResize"
CursorSResize Cursor = "SResize"
CursorWResize Cursor = "WResize"
CursorVerticalText Cursor = "VerticalText"
CursorCrosshair Cursor = "Crosshair"
CursorCell Cursor = "Cell"
)

View File

@ -3,54 +3,127 @@ package data
import "io"
import "bytes"
import "strings"
// Data represents arbitrary polymorphic data that can be used for data transfer
// between applications.
type Data map[Mime] io.ReadSeekCloser
type Data interface {
// Convert converts the data to the specified MIME type and returns it.
// If the type is not supported, this behavior will return nil, and
// false for ok. Note that Convert may be called multiple times for the
// same MIME type, so it must not return the same reader.
Convert (Mime) (reader io.ReadSeekCloser, ok bool)
// Supported returns a slice of MIME types that Convert can accept.
Supported () []Mime
}
// Mime represents a MIME type.
type Mime struct {
// Type is the first half of the MIME type, and Subtype is the second
// half. The separating slash is not included in either. For example,
// text/html becomes:
// Mime { Type: "text", Subtype: "html" }
// Mime { Type: "text", Subtype: "html" }
// The subtype is stored here including the tree and the suffix.
Type, Subtype string
// Charset is an optional field applicable to text types that specifies
// the character encoding of the data. If empty, UTF-8 is conventionally
// assumed because it's the only text encoding worth using.
Charset string
}
// M is shorthand for creating a MIME type.
func M (ty, subtype string) Mime {
return Mime { ty, subtype }
return Mime {
Type: strings.ToLower(ty),
Subtype: strings.ToLower(subtype),
}
}
// ParseMime parses a MIME type from text.
func ParseMime (text string) Mime {
ty, subty, _ := strings.Cut(text, "/")
subty, parameters, _ := strings.Cut(subty, ";")
mime := M(ty, subty)
for _, parameter := range strings.Split(parameters, " ") {
if parameter == "" { continue }
key, val, _ := strings.Cut(parameter, "=")
// TODO handle things like quoted values
val = strings.TrimSpace(val)
switch strings.TrimSpace(strings.ToLower(key)) {
case "charset":
mime.Charset = val
}
}
return mime
}
// MimePlain returns the MIME type of plain text.
func MimePlain () Mime { return Mime { Type: "text", Subtype: "plain" } }
// MimeFile returns the MIME type of a file path/URI.
func MimeFile () Mime { return Mime { Type: "text", Subtype: "uri-list" } }
// String returns the string representation of the MIME type.
func (mime Mime) String () string {
return mime.Type + "/" + mime.Subtype
out := mime.Type + "/" + mime.Subtype
if mime.Charset != "" {
out += "; charset=" + mime.Charset
}
return out
}
func MimePlain () Mime { return Mime { "text", "plain" } }
func MimeFile () Mime { return Mime { "text", "uri-list" } }
// FromText returns plain text Data given a string.
func FromText (text string) Data {
return FromBytes(MimePlain(), []byte(text))
}
type byteReadCloser struct { *bytes.Reader }
func (byteReadCloser) Close () error { return nil }
// Text returns plain text Data given a string.
func Text (text string) Data {
return Bytes(MimePlain(), []byte(text))
type bytesData struct {
mime Mime
buffer []byte
}
// Bytes constructs a Data given a buffer and a mime type.
func Bytes (mime Mime, buffer []byte) Data {
return Data {
mime: byteReadCloser { bytes.NewReader(buffer) },
// FromBytes constructs a Data given a buffer and a mime type.
func FromBytes (mime Mime, buffer []byte) Data {
return bytesData {
mime: mime,
buffer: buffer,
}
}
// Merge combines several Datas together. If multiple Datas provide a reader for
// the same mime type, the ones further on in the list will take precedence.
func Merge (individual ...Data) (combined Data) {
for _, data := range individual {
for mime, reader := range data {
combined[mime] = reader
}}
return
func (bytesDat bytesData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
if mime != bytesDat.mime { return nil, false }
return byteReadCloser { bytes.NewReader(bytesDat.buffer) }, true
}
func (bytesDat bytesData) Supported () []Mime {
return []Mime { bytesDat.mime }
}
type mergedData []Data
func (merged mergedData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
for _, individual := range merged {
if reader, ok := individual.Convert(mime); ok {
return reader, ok
}
}
return nil, false
}
func (merged mergedData) Supported () (supported []Mime) {
for _, individual := range merged {
supported = append(individual.Supported(), supported...)
}
return supported
}
// Merge combines several Datas together. If multiple Datas provide a reader for
// the same mime type, the ones first in the list will take precedence.
func Merge (individual ...Data) (combined Data) {
return mergedData(individual)
}

View File

@ -1,52 +1,98 @@
// Package event provides a system for broadcasting events to multiple event
// handlers.
package event
// A cookie is returned when you add an event handler so you can remove it
// later if you so choose.
type Cookie interface {
// Close removes the event handler this cookie is for.
Close ()
import "io"
import "iter"
import "errors"
// Cookie is returned when you add an event handler so you can remove it later
// if you so choose. When the Close behavior is called, the handler must be
// removed, even if an error is returned.
type Cookie io.Closer
// FuncCookie is a cookie that calls a function (itself) when closed.
type FuncCookie func () error
func (cookie FuncCookie) Close () error { return cookie () }
// NoCookie is a cookie that does nothing when closed.
type NoCookie struct { }
func (NoCookie) Close () error { return nil }
type multiCookie []Cookie
// MultiCookie creates a single cookie that, when closed, closes a list of other
// cookies.
func MultiCookie (cookies ...Cookie) Cookie {
return multiCookie(cookies)
}
// Broadcaster manages event listeners
type Broadcaster struct {
func (cookies multiCookie) Close () error {
errs := make([]error, len(cookies))
for index, cookie := range cookies {
errs[index] = cookie.Close()
}
return errors.Join(errs...)
}
// Broadcaster manages event listeners.
type Broadcaster[L any] struct {
lastID int
listeners map[int] func ()
listeners map[int] L
}
// Connect adds a new listener to the broadcaster and returns a corresponding
// cookie.
func (broadcaster *Broadcaster) Connect (listener func ()) Cookie {
if listener == nil { return nil }
if broadcaster.listeners == nil {
broadcaster.listeners = make(map[int] func ())
}
// Cookie.
func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
broadcaster.ensure()
cookie := broadcaster.newCookie()
broadcaster.listeners[cookie.id] = listener
return cookie
}
// Broadcast runs all event listeners at once.
func (broadcaster *Broadcaster) Broadcast () {
for _, listener := range broadcaster.listeners {
listener()
// Listeners returns an iterator over all listeners.
func (broadcaster *Broadcaster[L]) Listeners () iter.Seq[L] {
broadcaster.ensure()
return func (yield func (listener L) bool) {
for _, listener := range broadcaster.listeners {
if yield(listener) { break }
}
}
}
func (broadcaster *Broadcaster) newCookie () cookie {
func (broadcaster *Broadcaster[L]) newCookie () broadcasterCookie[L] {
broadcaster.lastID ++
return cookie {
return broadcasterCookie[L] {
id: broadcaster.lastID,
broadcaster: broadcaster,
}
}
type cookie struct {
id int
broadcaster *Broadcaster
func (broadcaster *Broadcaster[L]) ensure () {
if broadcaster.listeners == nil {
broadcaster.listeners = make(map[int] L)
}
}
func (cookie cookie) Close () {
delete(cookie.broadcaster.listeners, cookie.id)
type broadcasterCookie[L any] struct {
id int
broadcaster *Broadcaster[L]
}
func (cookie broadcasterCookie[L]) Close () error {
delete(cookie.broadcaster.listeners, cookie.id)
return nil
}
// FuncBroadcaster is a Broadcaster that manages functions with no arguments.
type FuncBroadcaster struct {
Broadcaster[func ()]
}
// Broadcast calls all connected listener funcs.
func (broadcaster *FuncBroadcaster) Broadcast () {
for listener := range broadcaster.Listeners() {
listener()
}
}

4
go.mod
View File

@ -1,5 +1,3 @@
module git.tebibyte.media/tomo/tomo
go 1.20
require golang.org/x/image v0.8.0
go 1.23.0

33
go.sum
View File

@ -1,33 +0,0 @@
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

400
icon.go Normal file
View File

@ -0,0 +1,400 @@
package tomo
import "fmt"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
// IconSize represents the size of an Icon.
type IconSize int; const (
IconSizeSmall IconSize = iota;
IconSizeMedium
IconSizeLarge
)
func (size IconSize) String () string {
switch size {
case IconSizeSmall: return "IconSizeSmall"
case IconSizeMedium: return "IconSizeMedium"
case IconSizeLarge: return "IconSizeLarge"
default: return fmt.Sprintf("IconSize(%d)", size)
}
}
// Icon represents an icon ID.
type Icon string
// A list of standard icon IDs. This is roughly based off of the XDG Icon Naming
// Specification (https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html).
const (
// IconUnknown should be a blank space the size of a regular Icon.
IconUnknown Icon = ""
// actions
IconAddressBookNew Icon = "AddressBookNew"
IconApplicationExit Icon = "ApplicationExit"
IconAppointmentNew Icon = "AppointmentNew"
IconCallStart Icon = "CallStart"
IconCallStop Icon = "CallStop"
IconContactNew Icon = "ContactNew"
// actions: dialog
IconDialogOkay Icon = "DialogOkay"
IconDialogCancel Icon = "DialogCancel"
// actions: edit
IconEditClear Icon = "EditClear"
IconEditCopy Icon = "EditCopy"
IconEditCut Icon = "EditCut"
IconEditDelete Icon = "EditDelete"
IconEditFind Icon = "EditFind"
IconEditFindReplace Icon = "EditFindReplace"
IconEditPaste Icon = "EditPaste"
IconEditRedo Icon = "EditRedo"
IconEditSelectAll Icon = "EditSelectAll"
IconEditUndo Icon = "EditUndo"
// actions: file
IconFileNew Icon = "FileNew"
IconDirectoryNew Icon = "DirectoryNew"
IconFileOpen Icon = "FileOpen"
IconFileOpenRecent Icon = "FileOpenRecent"
IconFilePageSetup Icon = "FilePageSetup"
IconFilePrint Icon = "FilePrint"
IconFilePrintPreview Icon = "FilePrintPreview"
IconFilePermissions Icon = "FilePermissions"
IconFileProperties Icon = "FileProperties"
IconFileRename Icon = "FileRename"
IconFileRevert Icon = "FileRevert"
IconFileSave Icon = "FileSave"
IconFileSaveAs Icon = "FileSaveAs"
IconFileSend Icon = "FileSend"
// actions: format
IconFormatIndentLess Icon = "FormatIndentLess"
IconFormatIndentMore Icon = "FormatIndentMore"
IconFormatAlignCenter Icon = "FormatAlignCenter"
IconFormatAlignEven Icon = "FormatAlignEven"
IconFormatAlignLeft Icon = "FormatAlignLeft"
IconFormatAlignRight Icon = "FormatAlignRight"
IconFormatTextDirectionLtr Icon = "FormatTextDirectionLtr"
IconFormatTextDirectionRtl Icon = "FormatTextDirectionRtl"
IconFormatTextBold Icon = "FormatTextBold"
IconFormatTextItalic Icon = "FormatTextItalic"
IconFormatTextUnderline Icon = "FormatTextUnderline"
IconFormatTextStrikethrough Icon = "FormatTextStrikethrough"
// actions: go
IconGoBottom Icon = "GoBottom"
IconGoDown Icon = "GoDown"
IconGoFirst Icon = "GoFirst"
IconGoHome Icon = "GoHome"
IconGoJump Icon = "GoJump"
IconGoLast Icon = "GoLast"
IconGoNext Icon = "GoNext"
IconGoPrevious Icon = "GoPrevious"
IconGoTop Icon = "GoTop"
IconGoUp Icon = "GoUp"
// actions: help
IconHelpAbout Icon = "HelpAbout"
IconHelpContents Icon = "HelpContents"
IconHelpFaq Icon = "HelpFaq"
// actions: insert
IconInsertImage Icon = "InsertImage"
IconInsertLink Icon = "InsertLink"
IconInsertObject Icon = "InsertObject"
IconInsertText Icon = "InsertText"
// actions: list
IconListAdd Icon = "ListAdd"
IconListRemove Icon = "ListRemove"
IconListChoose Icon = "ListChoose"
IconListExpand Icon = "ListExpand"
IconListContract Icon = "ListContract"
// actions: mail
IconMailForward Icon = "MailForward"
IconMailMarkImportant Icon = "MailMarkImportant"
IconMailMarkJunk Icon = "MailMarkJunk"
IconMailMarkNotJunk Icon = "MailMarkNotJunk"
IconMailMarkRead Icon = "MailMarkRead"
IconMailMarkUnread Icon = "MailMarkUnread"
IconMailMessageNew Icon = "MailMessageNew"
IconMailReplyAll Icon = "MailReplyAll"
IconMailReplySender Icon = "MailReplySender"
IconMailSend Icon = "MailSend"
IconMailReceive Icon = "MailReceive"
// actions: media
IconMediaEject Icon = "MediaEject"
IconMediaPlaybackPause Icon = "MediaPlaybackPause"
IconMediaPlaybackStart Icon = "MediaPlaybackStart"
IconMediaPlaybackStop Icon = "MediaPlaybackStop"
IconMediaRecord Icon = "MediaRecord"
IconMediaSeekBackward Icon = "MediaSeekBackward"
IconMediaSeekForward Icon = "MediaSeekForward"
IconMediaSkipBackward Icon = "MediaSkipBackward"
IconMediaSkipForward Icon = "MediaSkipForward"
// actions: object
IconObjectFlipHorizontal Icon = "ObjectFlipHorizontal"
IconObjectFlipVertical Icon = "ObjectFlipVertical"
IconObjectRotateLeft Icon = "ObjectRotateLeft"
IconObjectRotateRight Icon = "ObjectRotateRight"
// actions: process
IconProcessStop Icon = "ProcessStop"
// actions: system
IconSystemLockScreen Icon = "SystemLockScreen"
IconSystemLogOut Icon = "SystemLogOut"
IconSystemRun Icon = "SystemRun"
IconSystemSearch Icon = "SystemSearch"
IconSystemReboot Icon = "SystemReboot"
IconSystemShutdown Icon = "SystemShutdown"
// actions: tools
IconToolsCheckSpelling Icon = "ToolsCheckSpelling"
// actions: value
IconValueIncrement Icon = "ValueIncrement"
IconValueDecrement Icon = "ValueDecrement"
IconValueReset Icon = "ValueReset"
// actions: view
IconViewFullscreen Icon = "ViewFullscreen"
IconViewRefresh Icon = "ViewRefresh"
IconViewRestore Icon = "ViewRestore"
IconViewSortAscending Icon = "ViewSortAscending"
IconViewSortDescending Icon = "ViewSortDescending"
// actions: window
IconWindowClose Icon = "WindowClose"
IconWindowNew Icon = "WindowNew"
// actions: zoom
IconZoomFitBest Icon = "ZoomFitBest"
IconZoomIn Icon = "ZoomIn"
IconZoomOriginal Icon = "ZoomOriginal"
IconZoomOut Icon = "ZoomOut"
// applications
// Keep these in sync with nasin.ApplicationRole!
IconApplication Icon = "Application" // generic
IconApplicationWebBrowser Icon = "ApplicationWebBrowser"
IconApplicationMesssanger Icon = "ApplicationMesssanger"
IconApplicationPhone Icon = "ApplicationPhone"
IconApplicationMail Icon = "ApplicationMail"
IconApplicationTerminalEmulator Icon = "ApplicationTerminalEmulator"
IconApplicationFileBrowser Icon = "ApplicationFileBrowser"
IconApplicationTextEditor Icon = "ApplicationTextEditor"
IconApplicationDocumentViewer Icon = "ApplicationDocumentViewer"
IconApplicationWordProcessor Icon = "ApplicationWordProcessor"
IconApplicationSpreadsheet Icon = "ApplicationSpreadsheet"
IconApplicationSlideshow Icon = "ApplicationSlideshow"
IconApplicationCalculator Icon = "ApplicationCalculator"
IconApplicationPreferences Icon = "ApplicationPreferences"
IconApplicationProcessManager Icon = "ApplicationProcessManager"
IconApplicationSystemInformation Icon = "ApplicationSystemInformation"
IconApplicationManual Icon = "ApplicationManual"
IconApplicationCamera Icon = "ApplicationCamera"
IconApplicationImageViewer Icon = "ApplicationImageViewer"
IconApplicationMediaPlayer Icon = "ApplicationMediaPlayer"
IconApplicationImageEditor Icon = "ApplicationImageEditor"
IconApplicationAudioEditor Icon = "ApplicationAudioEditor"
IconApplicationVideoEditor Icon = "ApplicationVideoEditor"
IconApplicationClock Icon = "ApplicationClock"
IconApplicationCalendar Icon = "ApplicationCalendar"
IconApplicationChecklist Icon = "ApplicationChecklist"
// categories: applications
IconApplications Icon = "Applications"
IconApplicationsAccessories Icon = "ApplicationsAccessories"
IconApplicationsDevelopment Icon = "ApplicationsDevelopment"
IconApplicationsEngineering Icon = "ApplicationsEngineering"
IconApplicationsGames Icon = "ApplicationsGames"
IconApplicationsGraphics Icon = "ApplicationsGraphics"
IconApplicationsInternet Icon = "ApplicationsInternet"
IconApplicationsMultimedia Icon = "ApplicationsMultimedia"
IconApplicationsOffice Icon = "ApplicationsOffice"
IconApplicationsScience Icon = "ApplicationsScience"
IconApplicationsSystem Icon = "ApplicationsSystem"
IconApplicationsUtilities Icon = "ApplicationsUtilities"
// categories: preferences
IconPreferences Icon = "Preferences"
IconPreferencesDesktop Icon = "PreferencesDesktop"
IconPreferencesPeripherals Icon = "PreferencesPeripherals"
IconPreferencesPersonal Icon = "PreferencesPersonal"
IconPreferencesSystem Icon = "PreferencesSystem"
IconPreferencesNetwork Icon = "PreferencesNetwork"
// devices
IconDevice Icon = "Device"
IconDeviceCamera Icon = "DeviceCamera"
IconDeviceWebCamera Icon = "DeviceWebCamera"
IconDeviceComputer Icon = "DeviceComputer"
IconDevicePda Icon = "DevicePda"
IconDevicePhone Icon = "DevicePhone"
IconDevicePrinter Icon = "DevicePrinter"
IconDeviceScanner Icon = "DeviceScanner"
IconDeviceMultimediaPlayer Icon = "DeviceMultimediaPlayer"
IconDeviceVideoDisplay Icon = "DeviceVideoDisplay"
IconDeviceAudioInput Icon = "DeviceAudioInput"
IconDeviceAudioOutput Icon = "DeviceAudioOutput"
// devices: hardware
IconHardware Icon = "Hardware"
IconHardwareCPU Icon = "HardwareCPU"
IconHardwareGPU Icon = "HardwareGPU"
IconHardwareRAM Icon = "HardwareRAM"
IconHardwareSoundCard Icon = "HardwareSoundCard"
IconHardwareNetworkAdapter Icon = "HardwareNetworkAdapter"
// devices: power
IconPowerBattery Icon = "PowerBattery"
// devices: storage
IconStorageHardDisk Icon = "StorageHardDisk"
IconStorageFloppyDisk Icon = "StorageFloppyDisk"
IconStorageSolidState Icon = "StorageSolidState"
IconStorageOptical Icon = "StorageOptical"
IconStorageFlashStick Icon = "StorageFlashStick"
IconStorageFlashCard Icon = "StorageFlashCard"
IconStorageMagneticTape Icon = "StorageMagneticTape"
// devices: input
IconInputGaming Icon = "InputGaming"
IconInputKeyboard Icon = "InputKeyboard"
IconInputMouse Icon = "InputMouse"
IconInputTablet Icon = "InputTablet"
// devices: network
IconNetworkWired Icon = "NetworkWired"
IconNetworkWireless Icon = "NetworkWireless"
IconNetworkCellular Icon = "NetworkCellular"
IconNetworkLocal Icon = "NetworkLocal"
IconNetworkInternet Icon = "NetworkInternet"
IconNetworkVPN Icon = "NetworkVPN"
IconNetworkServer Icon = "NetworkServer"
IconNetworkWorkgroup Icon = "NetworkWorkgroup"
// emblems
IconEmblemDefault Icon = "EmblemDefault"
IconEmblemEncrypted Icon = "EmblemEncrypted"
IconEmblemFavorite Icon = "EmblemFavorite"
IconEmblemImportant Icon = "EmblemImportant"
IconEmblemReadOnly Icon = "EmblemReadOnly"
IconEmblemShared Icon = "EmblemShared"
IconEmblemSymbolicLink Icon = "EmblemSymbolicLink"
IconEmblemSynchronized Icon = "EmblemSynchronized"
IconEmblemSystem Icon = "EmblemSystem"
IconEmblemUnreadable Icon = "EmblemUnreadable"
// places
IconPlaceDirectory Icon = "PlaceDirectory"
IconPlaceRemote Icon = "PlaceRemote"
IconPlaceHome Icon = "PlaceHome"
IconPlaceDownloads Icon = "PlaceDownloads"
IconPlaceDesktop Icon = "PlaceDesktop"
IconPlacePhotos Icon = "PlacePhotos"
IconPlaceBooks Icon = "PlaceBooks"
IconPlaceBookmarks Icon = "PlaceBookmarks"
IconPlaceTrash Icon = "PlaceTrash"
IconPlaceDocuments Icon = "PlaceDocuments"
IconPlaceRepositories Icon = "PlaceRepositories"
IconPlaceMusic Icon = "PlaceMusic"
IconPlaceArchives Icon = "PlaceArchives"
IconPlaceFonts Icon = "PlaceFonts"
IconPlaceBinaries Icon = "PlaceBinaries"
IconPlaceVideos Icon = "PlaceVideos"
IconPlace3DObjects Icon = "Place3DObjects"
IconPlaceHistory Icon = "PlaceHistory"
IconPlacePreferences Icon = "PlacePreferences"
// status: appointments
IconAppointmentMissed Icon = "AppointmentMissed"
IconAppointmentSoon Icon = "AppointmentSoon"
// status: dialogs
IconDialogError Icon = "DialogError"
IconDialogInformation Icon = "DialogInformation"
IconDialogPassword Icon = "DialogPassword"
IconDialogQuestion Icon = "DialogQuestion"
IconDialogWarning Icon = "DialogWarning"
// status: directories
IconDirectoryDragAccept Icon = "DirectoryDragAccept"
IconDirectoryFull Icon = "DirectoryFull"
IconDirectoryOpen Icon = "DirectoryOpen"
IconDirectoryVisiting Icon = "DirectoryVisiting"
// status: trash
IconTrashFull Icon = "TrashFull"
// status: resource
IconResourceLoading Icon = "ResourceLoading"
IconResourceMissing Icon = "ResourceMissing"
// status: mail
IconMailAttachment Icon = "MailAttachment"
IconMailUnread Icon = "MailUnread"
IconMailReplied Icon = "MailReplied"
IconMailSigned Icon = "MailSigned"
IconMailSignedVerified Icon = "MailSignedVerified"
// status: network
IconCellularSignal0 Icon = "CellularSignal0"
IconCellularSignal1 Icon = "CellularSignal1"
IconCellularSignal2 Icon = "CellularSignal2"
IconCellularSignal3 Icon = "CellularSignal3"
IconWirelessSignal0 Icon = "WirelessSignal0"
IconWirelessSignal1 Icon = "WirelessSignal1"
IconWirelessSignal2 Icon = "WirelessSignal2"
IconWirelessSignal3 Icon = "WirelessSignal3"
IconNetworkError Icon = "NetworkError"
IconNetworkIdle Icon = "NetworkIdle"
IconNetworkOffline Icon = "NetworkOffline"
IconNetworkReceive Icon = "NetworkReceive"
IconNetworkTransmit Icon = "NetworkTransmit"
IconNetworkTransmitReceive Icon = "NetworkTransmitReceive"
// status: print
IconPrintError Icon = "PrintError"
IconPrintPrinting Icon = "PrintPrinting"
// status: security
IconSecurityHigh Icon = "SecurityHigh"
IconSecurityMedium Icon = "SecurityMedium"
IconSecurityLow Icon = "SecurityLow"
// status: software
IconSoftwareUpdateAvailable Icon = "SoftwareUpdateAvailable"
IconSoftwareUpdateUrgent Icon = "SoftwareUpdateUrgent"
IconSoftwareInstalling Icon = "SoftwareInstalling"
// status: sync
IconSyncError Icon = "SyncError"
IconSyncSynchronizing Icon = "SyncSynchronizing"
// status: tasks
IconTaskDue Icon = "TaskDue"
IconTaskPastDue Icon = "TaskPastDue"
// status: users
IconUserAvailable Icon = "UserAvailable"
IconUserAway Icon = "UserAway"
IconUserIdle Icon = "UserIdle"
IconUserOffline Icon = "UserOffline"
// status: power
IconBattery0 Icon = "Battery0"
IconBattery1 Icon = "Battery1"
IconBattery2 Icon = "Battery2"
IconBattery3 Icon = "Battery3"
IconBrightness0 Icon = "Brightness0"
IconBrightness1 Icon = "Brightness1"
IconBrightness2 Icon = "Brightness2"
IconBrightness3 Icon = "Brightness3"
// status: media
IconVolume0 Icon = "Volume0"
IconVolume1 Icon = "Volume1"
IconVolume2 Icon = "Volume2"
IconVolume3 Icon = "Volume3"
IconPlaylistRepeat Icon = "PlaylistRepeat"
IconPlaylistShuffle Icon = "PlaylistShuffle"
// status: weather
IconWeatherClear Icon = "WeatherClear"
IconWeatherClearNight Icon = "WeatherClearNight"
IconWeatherFewClouds Icon = "WeatherFewClouds"
IconWeatherFewCloudsNight Icon = "WeatherFewCloudsNight"
IconWeatherFog Icon = "WeatherFog"
IconWeatherOvercast Icon = "WeatherOvercast"
IconWeatherSevereAlert Icon = "WeatherSevereAlert"
IconWeatherShowers Icon = "WeatherShowers"
IconWeatherShowersScattered Icon = "WeatherShowersScattered"
IconWeatherSnow Icon = "WeatherSnow"
IconWeatherStorm Icon = "WeatherStorm"
)
// Texture returns a canvas.Texture of the corresponding icon ID. It may be
// closed and therefore rendered invalid if the icon set changes, so it is
// necessary to subscribe to the IconSetChange event in order to get a new icon
// texture when this happens.
func (id Icon) Texture (size IconSize) canvas.Texture {
assertBackend()
return backend.IconTexture(id, size)
}
// MimeIconTexture returns a canvas.Texture of the icon corresponding to a MIME
// type. It may be closed and therefore rendered invalid if the icon set
// changes, so it is necessary to subscribe to the IconSetChange event in order
// to get a new icon texture when this happens.
func MimeIconTexture (mime data.Mime, size IconSize) canvas.Texture {
assertBackend()
return backend.MimeIconTexture(mime, size)
}

View File

@ -59,6 +59,18 @@ const (
KeyF10 Key = 138
KeyF11 Key = 139
KeyF12 Key = 140
KeyF13 Key = 141
KeyF14 Key = 142
KeyF15 Key = 143
KeyF16 Key = 144
KeyF17 Key = 145
KeyF18 Key = 146
KeyF19 Key = 147
KeyF20 Key = 148
KeyF21 Key = 149
KeyF22 Key = 150
KeyF23 Key = 151
KeyF24 Key = 152
)
// Button represents a mouse button.
@ -95,11 +107,80 @@ func (key Key) Printable () (printable bool) {
// Modifiers lists what modifier keys are being pressed. These should be used
// instead of attempting to track the state of the modifier keys, because there
// is no guarantee that one press event will be coupled with one release event.
type Modifiers struct {
Shift bool
Control bool
Alt bool
Meta bool
Super bool
Hyper bool
type Modifiers uint; const (
ModNone Modifiers = 0
ModShift Modifiers = 1 << iota
ModControl
ModAlt
ModMeta
ModSuper
ModHyper
)
// Shift returns whether the list of modifiers includes the shift key.
func (modifiers Modifiers) Shift () bool {
return modifiers & ModShift != ModNone
}
// Control returns whether the list of modifiers includes the control key.
func (modifiers Modifiers) Control () bool {
return modifiers & ModControl != ModNone
}
// Alt returns whether the list of modifiers includes the alt key.
func (modifiers Modifiers) Alt () bool {
return modifiers & ModAlt != ModNone
}
// Meta returns whether the list of modifiers includes the meta key.
func (modifiers Modifiers) Meta () bool {
return modifiers & ModAlt != ModNone
}
// Super returns whether the list of modifiers includes the super key.
func (modifiers Modifiers) Super () bool {
return modifiers & ModSuper != ModNone
}
// Hyper returns whether the list of modifiers includes the hyper key.
func (modifiers Modifiers) Hyper () bool {
return modifiers & ModHyper != ModNone
}
// KeyChord combines a keyboard key with Modifiers.
type KeyChord struct {
Key Key
Modifiers Modifiers
}
// KC is a convenience constructor for a KeyChord.
func KC (key Key, modifiers Modifiers) KeyChord {
return KeyChord {
Key: key,
Modifiers: modifiers,
}
}
// Pressed returns true if the given Key Modifiers match the KeyChord.
func (chord KeyChord) Pressed (key Key, modifiers Modifiers) bool {
return chord == KC(key, modifiers)
}
// ButtonChord combines a mouse button with a number of modifiers.
type ButtonChord struct {
Button Button
Modifiers Modifiers
}
// BC is a convenience constructor for a ButtonChord.
func BC (button Button, modifiers Modifiers) ButtonChord {
return ButtonChord {
Button: button,
Modifiers: modifiers,
}
}
// Pressed returns true if the given Button and Modifiers match the ButtonChord.
func (chord ButtonChord) Pressed (button Button, modifiers Modifiers) bool {
return chord == BC(button, modifiers)
}

427
object.go
View File

@ -1,115 +1,376 @@
package tomo
import "image"
import "image/color"
import "golang.org/x/image/font"
import "git.tebibyte.media/tomo/tomo/text"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/event"
import "git.tebibyte.media/tomo/tomo/input"
import "git.tebibyte.media/tomo/tomo/canvas"
type Inset [4]int
type Gap image.Point
type Border struct {
Width Inset
Color [4]color.Color
}
type Align int; const (
AlignStart Align = iota // similar to left-aligned text
AlignMiddle // similar to center-aligned text
AlignEnd // similar to right-aligned text
AlignEven // similar to justified text
)
// Object is any onscreen object that is linked to a box (or is that box).
// Unlike the Box interface and associated interfaces, Object implementations
// may originate from anywhere.
type Object interface {
Box () Box
GetBox () Box
}
type Box interface {
// ContentObject is an Object that contains some kind of content.
type ContentObject interface {
Object
Bounds () image.Rectangle
InnerBounds () image.Rectangle
SetBounds (image.Rectangle)
SetColor (color.Color)
SetBorder (...Border)
SetMinimumSize (int, int)
SetPadding (Inset)
SetDNDData (data.Data)
SetDNDAccept (...data.Mime)
SetFocused (bool)
SetFocusable (bool)
Focused () bool
Modifiers (func ()) input.Modifiers
MousePosition (func ()) image.Point
OnFocusEnter (func ()) event.Cookie
OnFocusLeave (func ()) event.Cookie
OnDNDEnter (func ()) event.Cookie
OnDNDLeave (func ()) event.Cookie
OnDNDDrop (func (data.Data)) event.Cookie
OnMouseEnter (func ()) event.Cookie
OnMouseLeave (func ()) event.Cookie
OnMouseMove (func ()) event.Cookie
OnMouseDown (func (input.Button)) event.Cookie
OnMouseUp (func (input.Button)) event.Cookie
OnScroll (func (deltaX, deltaY float64)) event.Cookie
OnKeyDown (func (key input.Key, numberPad bool)) event.Cookie
OnKeyUp (func (key input.Key, numberPad bool)) event.Cookie
ContentBounds () image.Rectangle
ScrollTo (image.Point)
// ContentBounds returns the bounds of the inner content of the Box
// relative to the Box's InnerBounds.
ContentBounds () image.Rectangle
// ScrollTo shifts the origin of the Box's content to the origin of the
// Box's InnerBounds, offset by the given point.
ScrollTo (image.Point)
// OnContentBoundsChange specifies a function to be called when the
// Box's ContentBounds or InnerBounds changes.
OnContentBoundsChange (func ()) event.Cookie
}
type TextBox interface {
Box
SetTextColor (color.Color)
SetFace (font.Face)
SetHAlign (Align)
SetVAlign (Align)
// Box is a basic box with no content. Implementations of Box, as well as any
// interface that embed Box, may only originate from a Backend.
type Box interface {
Object
// Window returns the Window this Box is a part of.
Window () Window
// Bounds returns the outer bounding image.Rectangle of the Box relative
// to the Window.
Bounds () image.Rectangle
// InnerBounds returns the inner bounding image.Rectangle of the box. It
// is the value of Bounds inset by the Box's border and padding.
InnerBounds () image.Rectangle
// Role returns this Box's role as set by SetRole.
Role () Role
// SetRole sets what role this Box takes on. It is used by the Backend
// for applying styling.
SetRole (Role)
// Tag returns whether or not a named tag exists. These are used by the
// Backend for applying styling, among other things. There are some
// special tags that are only and always extant during certain user
// input states:
// - hovered: The mouse pointer is within the Box
// - focused: The Box has keyboard focus
// - pressed: The Box is being pressed by config.ButtonChordInteract
Tag (string) bool
// SetTag adds or removes a named tag.
SetTag (string, bool)
// SetAttr sets a style attribute, overriding the currently applied
// style.
SetAttr (Attr)
// UnsetAttr reverts a style attribute to whatever is specified by the
// currently applied style.
UnsetAttr (AttrKind)
// SetDNDData sets the data that will be picked up if this Box is
// dragged. If this is nil (which is the default), this Box will not be
// picked up.
SetDNDData (data.Data)
// SetDNDAccept sets the types of data which can be dropped onto this
// Box. If none are specified (which is the default), this Box will
// reject all drops.
SetDNDAccept (...data.Mime)
// SetFocused sets whether or not this Box has keyboard focus. If set to
// true, this method will steal focus away from whichever Object
// currently has focus.
SetFocused (bool)
// SetFocusable sets whether or not this Box can receive keyboard focus.
// If set to false and the Box is already focused. the focus is removed.
SetFocusable (bool)
// These are event subscription behaviors that allow callbacks to be
// connected to particular events. Multiple callbacks may be connected
// to the same event at once. Callbacks can be removed by closing the
// returned event.Cookie.
OnFocusEnter (func () ) event.Cookie
OnFocusLeave (func () ) event.Cookie
OnStyleChange (func () ) event.Cookie
OnIconSetChange (func () ) event.Cookie
OnDNDEnter (func () ) event.Cookie
OnDNDLeave (func () ) event.Cookie
OnDNDDrop (func (data.Data)) event.Cookie
OnMouseEnter (func () ) event.Cookie
OnMouseLeave (func () ) event.Cookie
// These event subscription behaviors require their callbacks to return
// a bool value. Under normal circumstances, these events are propagated
// to the Box which is most directly affected by them, and then to all
// of its parents from the bottom-up. Returning true from the callback
// will cause the propagation to stop immediately, thereby "catching"
// the event.
//
// Generally, if the event was successfully handled, the callbacks ought
// to return true. Additionally, when subscribing to an event that is
// often paired with a second one (for example, KeyDown and KeyUp), it
// is good practice to subscribe to both and return true/false under the
// same circumstances.
OnMouseMove (func () bool) event.Cookie
OnButtonDown (func (button input.Button) bool) event.Cookie
OnButtonUp (func (button input.Button) bool) event.Cookie
OnScroll (func (deltaX, deltaY float64) bool) event.Cookie
OnKeyDown (func (key input.Key, numberPad bool) bool) event.Cookie
OnKeyUp (func (key input.Key, numberPad bool) bool) event.Cookie
}
// CanvasBox is a Box that can be drawn to.
type CanvasBox interface {
Box
SetDrawer (canvas.Drawer)
// SetDrawer sets the canvas.Drawer that will be called upon to draw the
// Box's content when it is invalidated. The Canvas passed to the Drawer
// will have these properties:
// - It will have the same origin (0, 0) as the Window which contains
// the CanvasBox.
// - Its Bounds will describe the portion of the CanvasBox visible on
// screen. Therefore, it should not be used to determine the
// position of anything drawn within it. The Bounds of the CanvasBox
// should be used for this purpose.
SetDrawer (canvas.Drawer)
// Invalidate causes the CanvasBox's area to be redrawn at the end of
// the event cycle, even if it wouldn't otherwise be. This will call the
// canvas.Drawer specified by SetDrawer.
Invalidate ()
}
type ContainerBox interface {
// SurfaceBox is a Box that can be drawn to via a hardware accelerated (or
// platform-specific) graphics context.
type SurfaceBox interface {
Box
SetGap (Gap)
Add (Object)
Delete (Object)
Insert (child Object, before Object)
Clear ()
Length () int
At (int) Object
SetLayout (Layout)
// Surface returns the underlying graphics context. The result must be
// asserted to the expected type or passed through a type switch before
// use. The exact type returned here depends on the Backend being used,
// and it is up to the application author to ensure that the application
// and Backend agree on it. Applications should fail gracefully if the
// expected type was not found. If the surface has been destroyed by the
// Backend, it will return nil.
Surface () any
// Invalidate causes the data within the surface to be pushed to the
// screen at the end of the event cycle, even if it wouldn't otherwise
// be.
Invalidate ()
// OnSizeChange specifies a function to be called when the size of the
// Box is changed, the surface is re-allocated, or the surface is
// destroyed. The application must call Surface() each time this event
// fires in order to not draw onto a closed surface.
OnSizeChange (func ())
}
type Window interface {
SetRoot (Object)
SetIcon (sizes []image.Image)
NewMenu (image.Rectangle) (Window, error)
NewModal (image.Rectangle) (Window, error)
Widget () (Window, error)
Copy (data.Data)
Paste (callback func (data.Data, error), accept ...data.Mime)
Close ()
Show ()
Hide ()
OnClose (func ()) event.Cookie
// ContentBox is a box that has some kind of content.
type ContentBox interface {
Box
// ContentBounds returns the bounds of the inner content of the Box
// relative to the Box's InnerBounds.
ContentBounds () image.Rectangle
// ScrollTo shifts the origin of the Box's content to the origin of the
// Box's InnerBounds, offset by the given image.Point.
ScrollTo (image.Point)
// OnContentBoundsChange specifies a function to be called when the
// Box's ContentBounds or InnerBounds changes.
OnContentBoundsChange (func ()) event.Cookie
}
type MainWindow interface {
Window
NewChild (image.Rectangle) (Window, error)
// TextBox is a box that contains text content.
type TextBox interface {
ContentBox
// SetText sets the text content of the Box.
SetText (string)
// SetSelectable sets whether or not the text content can be
// highlighted/selected.
SetSelectable (bool)
// Select sets the text cursor or selection.
Select (text.Dot)
// Dot returns the text cursor or selection.
Dot () text.Dot
// OnDotChange specifies a function to be called when the text cursor or
// selection changes.
OnDotChange (func ()) event.Cookie
}
// ContentBox is a box that can contain child Objects. It arranges them
// according to a layout rule.
type ContainerBox interface {
ContentBox
// Add appends a child Object. If the Object is already a child of
// another Object, it will be removed from that Object first.
Add (Object)
// Remove removes a child Object, if it is a child of this Box.
Remove (Object)
// Insert inserts a child Object before a specified Object. If the
// before Object is nil or is not contained within this Box, the
// inserted Object is appended.
Insert (child Object, before Object)
// Clear removes all child Objects.
Clear ()
// Len returns the amount of child Objects.
Len () int
// At returns the child Object at the specified index.
At (int) Object
// SetInputMask sets whether or not user input events will be sent to
// this Box's children. If false, which is the default, input events
// will be sent to this Box as well as all of its children. If true,
// any input events that would otherwise go to this Box's children are
// sent to it instead. This prevents children from performing event
// catching as described in the documentation for Box.
SetInputMask (bool)
// TODO: if it turns out selecting specific kinds of events to mask off
// is a good idea, have SetInputMask take in a vararg list of event
// types.
}
// LayoutHints are passed to a layout to tell it how to arrange child Boxes.
type LayoutHints struct {
// Bounds is the bounding rectangle that children should be placed
// within. Any padding values are already applied.
Bounds image.Rectangle
// OverflowX and OverflowY control whether child Boxes may be positioned
// outside of Bounds.
OverflowX bool
OverflowY bool
// AlignX and AlignY control how child Boxes are aligned horizontally
// and vertically. The effect of this may vary depending on the Layout.
AlignX Align
AlignY Align
// Gap controls the amount of horizontal and vertical spacing in-between
// child Boxes.
Gap image.Point
}
// A Layout can be given to a ContainerBox to arrange child objects.
type Layout interface {
Arrange (image.Rectangle, Gap, []Box)
// MinimumSize returns the minimum width and height of
// LayoutHints.Bounds needed to properly lay out all child Boxes.
MinimumSize (LayoutHints, BoxQuerier) image.Point
// Arrange arranges child Boxes according to the given LayoutHints.
Arrange (LayoutHints, BoxArranger)
// RecommendedHeight returns the recommended height for a given width,
// if supported. Otherwise, it should just return the minimum height.
// The result of this behavior may or may not be respected, depending on
// the situation.
RecommendedHeight (LayoutHints, BoxQuerier, int) int
// RecommendedWidth returns the recommended width for a given height, if
// supported. Otherwise, it should just return the minimum width. The
// result of this behavior may or may not be respected, depending on the
// situation.
RecommendedWidth (LayoutHints, BoxQuerier, int) int
}
// BoxQuerier allows the attributes of a ContainerBox's children to be queried.
type BoxQuerier interface {
// Len returns the amount of Boxes.
Len () int
// MinimumSize returns the minimum size of a Box.
MinimumSize (index int) image.Point
// RecommendedWidth returns the recommended width for a given height for
// a Box, if supported. Otherwise, it should just return the minimum
// width of that Box. The result of this behavior may or may not be
// respected, depending on the situation.
RecommendedWidth (index int, height int) int
// RecommendedHeight returns the recommended height for a given width
// for a Box, if supported. Otherwise, it should just return the minimum
// width of that Box. The result of this behavior may or may not be
// respected, depending on the situation.
RecommendedHeight (index int, width int) int
}
// BoxArranger is a BoxQuerier that allows arranging child boxes according to a
// layout.
type BoxArranger interface {
BoxQuerier
// SetBounds sets the bounds of a Box.
SetBounds (index int, bounds image.Rectangle)
}
// WindowKind specifies a Window's kind, which determines how it is displayed
// and managed by the operating system.
type WindowKind string; const (
// Normal is a normal Window.
WindowKindNormal WindowKind = "Normal"
// Plain is an undecorated Window that does not appear in window lists.
// It is intended for things like docks, panels, etc.
WindowKindPlain WindowKind = "Plain"
// Utility is a small Window for toolboxes, command palletes, etc. It is
// usually given special styling and management by the OS.
WindowKindUtility WindowKind = "Utility"
// Turn is a small Window for menus and panes which have been "torn off"
// from their main Window or position. It is usually given special
// styling and management by the OS.
WindowKindTorn WindowKind = "Toolbar"
// Menu is an undecorated Window for drop down menus, context menus,
// etc. It closes once the user interacts outside of it.
WindowKindMenu WindowKind = "Menu"
// Modal, while open, blocks all user input from reaching its parent
// window, forcing the user's attention. It is usually given special
// styling and management by the OS. Note that in some environments it
// will not be given window controls, so it should contain some "Close"
// or "Cancel" button.
WindowKindModal WindowKind = "Modal"
)
// Window is an operating system Window. It can directly contain one object,
// which is usually a container. Windows themselves are completely transparent,
// and become opaque once an opaque object is added as their root.
type Window interface {
// Bounds returns the bounds of the Window including its frame, if
// possible. This means that the top-left point of the bounds will be
// either zero or negative.
Bounds () image.Rectangle
// InnerBounds returns the inner bounds of the Window, not including its
// frame. This means that the top-left point of the bounds will be zero.
InnerBounds () image.Rectangle
// SetRoot sets the root child of the Window. There can only be one at
// a time, and setting it will remove the current child if there is one.
SetRoot (Object)
// SetTitle sets the title of the Window.
SetTitle (string)
// SetIcon sets the icon of the Window.
SetIcon (Icon)
// SetResizable sets whether the Window can be resized by the user in
// the X and Y directions. If one or both axes are false, the ones that
// are will be shrunk to the Window's minimum size.
SetResizable (x, y bool)
// SetBounds sets this Window's bounds. This may or may not have any
// effect on the Window's position on screen depending on the platform.
SetBounds (image.Rectangle)
// NewChild creates a new Window that is semantically a child of this
// Window. It does not actually reside within this Window, but it may be
// linked to it via some other means.
NewChild (WindowKind, image.Rectangle) (Window, error)
// Modifiers returns which modifier keys on the keyboard are currently
// being held down.
Modifiers () input.Modifiers
// MousePosition returns the position of the mouse pointer relative to
// the Window.
MousePosition () image.Point
// Copy copies data to the clipboard.
Copy (data.Data)
// Paste reads data from the clipboard. When the data is available or an
// error has occurred, the provided function will be called.
Paste (callback func (data.Data, error), accept ...data.Mime)
// SetVisible sets whether or not this window is visible. Windows are
// invisible by default.
SetVisible (bool)
// Visible returns whether or not this window is visible.
Visible () bool
// Close closes the window. This does not trigger the TryClose event.
Close () error
// OnTryClose specifies a function to be called when the user attempts
// to close the Window. If any registered handlers returns false, the
// Window will not be closed. This can be used to display some sort of
// "Unsaved changes" warning to the user.
OnTryClose (func () bool) event.Cookie
// OnClose specifies a function to be called when the Window is closed.
OnClose (func ()) event.Cookie
}

View File

@ -1,55 +0,0 @@
package tomo
import "os"
import "plugin"
import "path/filepath"
var pluginPaths []string
func loadPlugins () {
for _, dir := range pluginPaths {
if dir != "" {
loadPluginsFrom(dir)
}
}
}
func loadPluginsFrom (dir string) {
entries, err := os.ReadDir(dir)
// its no big deal if one of the dirs doesn't exist
if err != nil { return }
for _, entry := range entries {
if entry.IsDir() { continue }
if filepath.Ext(entry.Name()) != ".so" { continue }
pluginPath := filepath.Join(dir, entry.Name())
loadPlugin(pluginPath)
}
}
func loadPlugin (path string) {
die := func (reason string) {
println("tomo: could not load plugin at", path + ":", reason)
}
plugin, err := plugin.Open(path)
if err != nil {
die(err.Error())
return
}
// check for and obtain basic plugin functions
name, ok := extract[func () string](plugin, "Name")
if !ok { die("does not implement Name() string"); return }
_, ok = extract[func () string](plugin, "Description")
if !ok { die("does not implement Description() string"); return }
println("tomo: loaded plugin", name())
}
func extract[T any] (plugin *plugin.Plugin, name string) (value T, ok bool) {
symbol, err := plugin.Lookup(name)
if err != nil { return }
value, ok = symbol.(T)
return
}

248
text/text.go Normal file
View File

@ -0,0 +1,248 @@
package text
import "unicode"
// Dot represents a cursor or text selection. It has a start and end position,
// referring to where the user began and ended the selection respectively.
type Dot struct { Start, End int }
// EmptyDot returns a zero-width dot at the specified position.
func EmptyDot (position int) Dot {
return Dot { position, position }
}
// Canon places the lesser value at the start, and the greater value at the end.
// Note that a canonized dot does not in all cases correspond directly to the
// original, because there is a semantic value to the start and end positions.
func (dot Dot) Canon () Dot {
if dot.Start > dot.End {
return Dot { dot.End, dot.Start }
} else {
return dot
}
}
// Empty returns whether or not the
func (dot Dot) Empty () bool {
return dot.Start == dot.End
}
// Add shifts the dot to the right by the specified amount.
func (dot Dot) Add (delta int) Dot {
return Dot {
dot.Start + delta,
dot.End + delta,
}
}
// Sub shifts the dot to the left by the specified amount.
func (dot Dot) Sub (delta int) Dot {
return Dot {
dot.Start - delta,
dot.End - delta,
}
}
// Constrain constrains the dot's start and end from zero to length (inclusive).
func (dot Dot) Constrain (length int) Dot {
if dot.Start < 0 { dot.Start = 0 }
if dot.Start > length { dot.Start = length }
if dot.End < 0 { dot.End = 0 }
if dot.End > length { dot.End = length }
return dot
}
// Width returns how many runes the dot spans.
func (dot Dot) Width () int {
dot = dot.Canon()
return dot.End - dot.Start
}
// Slice returns the subset of text that the dot covers.
func (dot Dot) Slice (text []rune) []rune {
dot = dot.Canon().Constrain(len(text))
return text[dot.Start:dot.End]
}
// WordToLeft returns how far away to the left the next word boundary is from a
// given position.
func WordToLeft (text []rune, position int) (length int) {
if position < 1 { return }
if position > len(text) { position = len(text) }
index := position - 1
for index >= 0 && unicode.IsSpace(text[index]) {
length ++
index --
}
for index >= 0 && !unicode.IsSpace(text[index]) {
length ++
index --
}
return
}
// WordToRight returns how far away to the right the next word boundary is from
// a given position.
func WordToRight (text []rune, position int) (length int) {
if position < 0 { return }
if position > len(text) { position = len(text) }
index := position
for index < len(text) && unicode.IsSpace(text[index]) {
length ++
index ++
}
for index < len(text) && !unicode.IsSpace(text[index]) {
length ++
index ++
}
return
}
// WordAround returns a dot that surrounds the word at the specified position.
func WordAround (text []rune, position int) (around Dot) {
return Dot {
position - WordToLeft(text, position),
position + WordToRight(text, position),
}
}
// Backspace deletes the rune to the left of the dot. If word is true, it
// deletes up until the next word boundary on the left. If the dot is non-empty,
// it deletes the text inside of the dot.
func Backspace (text []rune, dot Dot, word bool) (result []rune, moved Dot) {
dot = dot.Constrain(len(text))
if dot.Empty() {
distance := 1
if word {
distance = WordToLeft(text, dot.End)
}
result = append (
result,
text[:dot.Sub(distance).Constrain(len(text)).End]...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Sub(distance).Start)
return
} else {
return Delete(text, dot, word)
}
}
// Delete deletes the rune to the right of the dot. If word is true, it deletes
// up until the next word boundary on the right. If the dot is non-empty, it
// deletes the text inside of the dot.
func Delete (text []rune, dot Dot, word bool) (result []rune, moved Dot) {
dot = dot.Constrain(len(text))
if dot.Empty() {
distance := 1
if word {
distance = WordToRight(text, dot.End)
}
result = append(result, text[:dot.End]...)
result = append (
result,
text[dot.Add(distance).Constrain(len(text)).End:]...)
moved = dot
return
} else {
dot = dot.Canon()
result = append(result, text[:dot.Start]...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Start)
return
}
}
// Lift removes the section of text inside of the dot, and returns a copy of it.
func Lift (text []rune, dot Dot) (result []rune, moved Dot, lifted []rune) {
dot = dot.Constrain(len(text))
if dot.Empty() {
moved = dot
return
}
dot = dot.Canon()
lifted = make([]rune, dot.Width())
copy(lifted, dot.Slice(text))
result = append(result, text[:dot.Start]...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Start)
return
}
// Type inserts one of more runes into the text at the dot position. If the dot
// is non-empty, it replaces the text inside of the dot with the new runes.
func Type (text []rune, dot Dot, characters ...rune) (result []rune, moved Dot) {
dot = dot.Constrain(len(text))
if dot.Empty() {
result = append(result, text[:dot.End]...)
result = append(result, characters...)
if dot.End < len(text) {
result = append(result, text[dot.End:]...)
}
moved = EmptyDot(dot.Add(len(characters)).End)
return
} else {
dot = dot.Canon()
result = append(result, text[:dot.Start]...)
result = append(result, characters...)
result = append(result, text[dot.End:]...)
moved = EmptyDot(dot.Add(len(characters)).Start)
return
}
}
// MoveLeft moves the dot left one rune. If word is true, it moves the dot to
// the next word boundary on the left.
func MoveLeft (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Canon().Constrain(len(text))
distance := 0
if dot.Empty() {
distance = 1
}
if word {
distance = WordToLeft(text, dot.Start)
}
moved = EmptyDot(dot.Sub(distance).Start)
return
}
// MoveRight moves the dot right one rune. If word is true, it moves the dot to
// the next word boundary on the right.
func MoveRight (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Canon().Constrain(len(text))
distance := 0
if dot.Empty() {
distance = 1
}
if word {
distance = WordToRight(text, dot.End)
}
moved = EmptyDot(dot.Add(distance).End)
return
}
// SelectLeft moves the end of the dot left one rune. If word is true, it moves
// the end of the dot to the next word boundary on the left.
func SelectLeft (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Constrain(len(text))
distance := 1
if word {
distance = WordToLeft(text, dot.End)
}
dot.End -= distance
return dot
}
// SelectRight moves the end of the dot right one rune. If word is true, it
// moves the end of the dot to the next word boundary on the right.
func SelectRight (text []rune, dot Dot, word bool) (moved Dot) {
dot = dot.Constrain(len(text))
distance := 1
if word {
distance = WordToRight(text, dot.End)
}
dot.End += distance
return dot
}

77
tomo.go
View File

@ -1,59 +1,86 @@
package tomo
import "sync"
import "image"
import "errors"
import "git.tebibyte.media/tomo/tomo/canvas"
var backend Backend
// TODO this really sucks. It might be a good idea to have Do be the entry point
// for every off-thread call, and Stop should just call backend.Stop within
// backend.Do. This is because Do is a queue and is not vulnerable to recursive
// locking.
// Run initializes a backend, runs the specified callback function, and runs the
// event loop in that order. This function blocks until Stop is called, or the
// backend experiences a fatal error.
func Run (callback func ()) error {
if backend != nil {
return errors.New("there is already a backend running")
}
back, err := Initialize()
if err != nil { return err }
backend = back
callback()
return backend.Run()
var stopping bool
var stoppingLock sync.Mutex
func isStopping () bool {
stoppingLock.Lock()
defer stoppingLock.Unlock()
return stopping
}
func setStopping (is bool) {
stoppingLock.Lock()
defer stoppingLock.Unlock()
stopping = is
}
func assertBackend () {
if backend == nil { panic("nil backend") }
}
// Stop stops the backend, unblocking run. Run may be called again after calling
// Stop.
// Stop stops the currently running Backend.
func Stop () {
assertBackend()
if isStopping() { return }
setStopping(true)
backendLock.Lock()
defer backendLock.Unlock()
if backend == nil { return }
backend.Stop()
backend = nil
setStopping(false)
}
func NewWindow (bounds image.Rectangle) MainWindow {
// Do performs a callback function in the event loop thread as soon as possible.
func Do (callback func ()) {
backendLock.Lock()
defer backendLock.Unlock()
if backend != nil { backend.Do(callback) }
}
// NewWindow creates and returns a Window within the specified bounds on screen.
func NewWindow (kind WindowKind, bounds image.Rectangle) (Window, error) {
assertBackend()
return backend.NewWindow(bounds)
return backend.NewWindow(kind, bounds)
}
// NewBox creates and returns a basic Box.
func NewBox () Box {
assertBackend()
return backend.NewBox()
}
// NewTextBox creates and returns a Box that can display text.
func NewTextBox () TextBox {
assertBackend()
return backend.NewTextBox()
}
// NewCanvasBox creates and returns a Box that can display custom graphics.
func NewCanvasBox () CanvasBox {
assertBackend()
return backend.NewCanvasBox()
}
// NewContainerBox creates and returns a Box that can contain other boxes.
func NewContainerBox () ContainerBox {
assertBackend()
return backend.NewContainerBox()
}
// NewTexture creates a new canvas.Texture from an image. When no longer in use,
// it must be freed using Close().
func NewTexture (source image.Image) canvas.TextureCloser {
assertBackend()
return backend.NewTexture(source)
}
// NewCanvas creates a new canvas with the specified bounds. When no longer in
// use, it must be freed using Close().
func NewCanvas (bounds image.Rectangle) canvas.CanvasCloser {
assertBackend()
return backend.NewCanvas(bounds)
}

225
unit.go Normal file
View File

@ -0,0 +1,225 @@
package tomo
import "fmt"
import "image"
import "image/color"
// Side represents one side of a rectangle.
type Side int; const (
SideTop Side = iota
SideRight
SideBottom
SideLeft
)
func (side Side) String () string {
switch side {
case SideTop: return "SideTop"
case SideRight: return "SideRight"
case SideBottom: return "SideBottom"
case SideLeft: return "SideLeft"
default: return fmt.Sprintf("Side(%d)", side)
}
}
// Inset represents a rectangle inset that can have a different value for each
// side.
type Inset [4]int
// I allows you to create an inset in a CSS-ish way:
//
// - One argument: all sides are set to this value
// - Two arguments: the top and bottom sides are set to the first value, and
// the left and right sides are set to the second value.
// - Three arguments: the top side is set by the first value, the left and
// right sides are set by the second vaue, and the bottom side is set by the
// third value.
// - Four arguments: each value corresponds to a side.
//
// This function will panic if an argument count that isn't one of these is
// given.
func I (sides ...int) Inset {
switch len(sides) {
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
default: panic("I: illegal argument count.")
}
}
func (inset Inset) String () string {
return fmt.Sprintf("%d %d %d %d", inset[0], inset[1], inset[2], inset[3])
}
// Apply returns the given image.Rectangle, shrunk on all four sides by the
// given Inset. If a measurment of the Inset is negative, that side will instead
// be expanded outward. If the rectangle's dimensions cannot be reduced any
// further, an empty rectangle near its center will be returned.
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
smaller = bigger
if smaller.Dx() < inset[3] + inset[1] {
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
smaller.Max.X = smaller.Min.X
} else {
smaller.Min.X += inset[3]
smaller.Max.X -= inset[1]
}
if smaller.Dy() < inset[0] + inset[2] {
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
smaller.Max.Y = smaller.Min.Y
} else {
smaller.Min.Y += inset[0]
smaller.Max.Y -= inset[2]
}
return
}
// Inverse returns a negated version of the Inset.
func (inset Inset) Inverse () (prime Inset) {
return Inset {
inset[0] * -1,
inset[1] * -1,
inset[2] * -1,
inset[3] * -1,
}
}
// Horizontal returns the sum of SideRight and SideLeft.
func (inset Inset) Horizontal () int {
return inset[SideRight] + inset[SideLeft]
}
// Vertical returns the sum of SideTop and SideBottom.
func (inset Inset) Vertical () int {
return inset[SideTop] + inset[SideBottom]
}
// Border represents a single border of a Box.
type Border struct {
Width Inset
Color [4]color.Color
}
func (border Border) String () string {
return fmt.Sprintf("%v %v %v %v / %v",
border.Color[0], border.Color[1], border.Color[2], border.Color[3],
border.Width)
}
// Align lists basic alignment types.
type Align int; const (
AlignStart Align = iota // similar to left-aligned text
AlignMiddle // similar to center-aligned text
AlignEnd // similar to right-aligned text
AlignEven // similar to justified text
)
func (align Align) String () string {
switch align {
case AlignStart: return "AlignStart"
case AlignMiddle: return "AlignMiddle"
case AlignEnd: return "AlignEnd"
case AlignEven: return "AlignEven"
default: return fmt.Sprintf("Align(%d)", align)
}
}
// TextureMode lists texture rendering modes.
type TextureMode int; const (
TextureModeTile TextureMode = iota
TextureModeCenter
// TODO more modes: fit, fill, and stretch
// Of course canvas will need to have a concept of this.
)
func (mode TextureMode) String () string {
switch mode {
case TextureModeTile: return "TextureModeTile"
case TextureModeCenter: return "TextureModeCenter"
default: return fmt.Sprintf("TextureMode(%d)", mode)
}
}
// Face represents a typeface.
type Face struct {
// Font specifies the font name. This should be searched for in a list
// of installed fonts.
Font string
// Size is the point size of the face.
Size float64
// Weight is the weight of the face. If zero, it should be interpreted
// as normal (equivalent to 400).
Weight int
// Italic is how italicized the face is. It ranges from 0 to 1. It is
// different from Slant in that it may alter the design of the glyphs
// instead of simply skewing them.
Italic float64
// Slant is how slanted the face is. It ranges from 0 to 1. It is
// different from Italic in that it simply skews the glyphs without
// altering their design.
Slant float64
}
func (face Face) String () string {
return fmt.Sprintf (
"%s %fpt W%d I%f S%f",
face.Font, face.Size, face.Weight, face.Italic, face.Slant)
}
// Role describes the role of an Object.
type Role struct {
// Package is an optional namespace field. If specified, it should be
// the package name or module name the object is from.
Package string
// Object specifies what type of Object it is. For example:
// - TextInput
// - Table
// - Label
// - Dial
// This should correspond directly to the type name of the Object.
Object string
}
// String satisfies the fmt.Stringer interface. It follows the format of:
// Package.Object
func (r Role) String () string {
return fmt.Sprintf("%s.%s", r.Package, r.Object)
}
// R is a convenience constructor for Role.
func R (pack, object string) Role {
return Role { Package: pack, Object: object }
}
// Color represents a color ID.
type Color int; const (
ColorBackground Color = iota
ColorForeground
ColorRaised
ColorSunken
ColorAccent
)
func (id Color) String () string {
switch id {
case ColorBackground: return "ColorBackground"
case ColorForeground: return "ColorForeground"
case ColorRaised: return "ColorRaised"
case ColorSunken: return "ColorSunken"
case ColorAccent: return "ColorAccent"
default: return fmt.Sprintf("Color(%d)", id)
}
}
// RGBA satisfies the color.Color interface. The result of this method may be
// rendered invalid if the visual style changes, so it is often necessary to
// subscribe to the StyleChange event in order to get new RGBA values when this
// happens.
func (id Color) RGBA () (r, g, b, a uint32) {
assertBackend()
return backend.ColorRGBA(id)
}

22
unix.go
View File

@ -1,22 +0,0 @@
//go:build linux || darwin || freebsd
package tomo
import "os"
import "strings"
import "path/filepath"
func init () {
pathVariable := os.Getenv("TOMO_PLUGIN_PATH")
pluginPaths = strings.Split(pathVariable, ":")
pluginPaths = append (
pluginPaths,
"/usr/lib/tomo/plugins",
"/usr/local/lib/tomo/plugins")
homeDir, err := os.UserHomeDir()
if err == nil {
pluginPaths = append (
pluginPaths,
filepath.Join(homeDir, ".local/lib/tomo/plugins"))
}
}