mirror of
https://github.com/davidgiven/fluxengine.git
synced 2025-10-24 11:11:02 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0457282d47 | ||
|
|
06d8609200 | ||
|
|
fa975b5511 |
12
Makefile
12
Makefile
@@ -1,18 +1,21 @@
|
||||
PACKAGES = zlib sqlite3 libusb-1.0 protobuf
|
||||
PACKAGES = zlib sqlite3 libusb-1.0 protobuf gtk+-3.0
|
||||
|
||||
export CFLAGS = -x c++ --std=gnu++2a -ffunction-sections -fdata-sections \
|
||||
export CFLAGS = -ffunction-sections -fdata-sections
|
||||
export CXXFLAGS = $(CFLAGS) \
|
||||
--std=gnu++2a \
|
||||
-Wno-deprecated-enum-enum-conversion \
|
||||
-Wno-deprecated-enum-float-conversion
|
||||
export LDFLAGS = -pthread
|
||||
|
||||
export COPTFLAGS = -Os
|
||||
export LDOPTFLAGS = -Os
|
||||
export LDOPTFLAGS = -Os -ldl
|
||||
|
||||
export CDBGFLAGS = -O0 -g
|
||||
export LDDBGFLAGS = -O0 -g
|
||||
export LDDBGFLAGS = -O0 -g -ldl
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
export PROTOC = /mingw32/bin/protoc
|
||||
export CC = /mingw32/bin/gcc
|
||||
export CXX = /mingw32/bin/g++
|
||||
export AR = /mingw32/bin/ar rc
|
||||
export RANLIB = /mingw32/bin/ranlib
|
||||
@@ -30,6 +33,7 @@ $(error You must have these pkg-config packages installed: $(PACKAGES))
|
||||
endif
|
||||
|
||||
export PROTOC = protoc
|
||||
export CC = gcc
|
||||
export CXX = g++
|
||||
export AR = ar rc
|
||||
export RANLIB = ranlib
|
||||
|
||||
@@ -230,3 +230,7 @@ As an exception, `dep/snowhouse` contains the snowhouse assertion library,
|
||||
taken from https://github.com/banditcpp/snowhouse. It is Boost Standard License
|
||||
1.0 licensed. Please see the contents of the directory for the full text.
|
||||
|
||||
As an exception, `dep/libui` contains the libui GUI library, taken from
|
||||
https://github.com/andlabs/libui. It is MIT licensed. Please see the contents
|
||||
of the directory for the full text.
|
||||
|
||||
|
||||
131
dep/libui/CONTRIBUTING.md
Normal file
131
dep/libui/CONTRIBUTING.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Contributing to libui
|
||||
|
||||
libui is an open source project that openly accepts contributions. I appreciate your help!
|
||||
|
||||
## Rules for contributing code
|
||||
|
||||
While libui is open to contributions, a number of recent, significantly large contributions and uncontributed forks have recently surfaced that do not present themselves in a form that makes it easy for libui to accept them. In order to give your contribution a high chance of being accepted into libui, please keep the following in mind as you prepare your contribution.
|
||||
|
||||
### Commit messages and pull request description
|
||||
|
||||
libui does not enforce rules about the length or detail that a commit message. I'm not looking for an essay. However, single-word descriptions of nontrivial changes are *not* acceptable. I should be able to get a glimpse of what a commit does from the commit message, even if it's just one sentence to describe a trivial change. (Yes, I know I haven't followed this rule strictly myself, but I try not to break it too.) And a commit message should encompass everything; typically, I make a number of incremental commits toward a feature, so the commit messages don't have to be too long to explain everything.
|
||||
|
||||
Your pull request description, on the other hand, must be a summary of the sum total of all the changes made to libui. Don't just drop a pull request on me with a one-line-long elevator pitch of what you added. Describe your proposed API changes, implementation requirements, and any important consequences of your work.
|
||||
|
||||
### Code formatting
|
||||
|
||||
libui uses K&R C formatting rules for overall code structure: spaces after keywords like `if`, `{` on the same line as a statement with a space, `{` on its own line after a function or method signature (even those inside the class body), no space after the name of a function, etc.
|
||||
|
||||
Use hard tabs, NOT spaces, for indentation. I use a proportional-width font and my text editor doesn't set tabs to a multiple of the space width, so I *will* be able to tell. If you use a fixed-width font, I suggest setting a tab width of 4 spaces per tab, but don't put diagrams in comments with hard tabs, because not everyone does this.
|
||||
|
||||
Expressions should have a space around binary operators, and use parentheses where it would help humans gather the meaning of an expression, regardless of whether a computer could tell what is correct.
|
||||
|
||||
When breaking expressions into multiple lines, always break *after* an operator, such as `,` or `&&`.
|
||||
|
||||
There should be a newline between a function's variables and a function's code. After that, you can place newlines to delimit different parts of a function, but don't go crazy.
|
||||
|
||||
In the event you are unsure of something, refer to existing libui code for examples. I may wind up fixing minor details later anyway, so don't fret about getting minor details right the first time.
|
||||
|
||||
### Naming
|
||||
|
||||
libui uses camel-case for naming, with a handful of very specific exceptions (namely GObject method names, where GObject itself enforces the naming convention).
|
||||
|
||||
All public API names should begin with `ui` and followed by a capital letter. All public struct field names should begin with a capital letter. This is identical to the visibiilty rules of Go, assuming a package name of `ui`.
|
||||
|
||||
Private API names — specifcally those used by more than one source file — should begin with `uipriv` and be followed by a capital letter. This avoids namespace collisions in static libraries.
|
||||
|
||||
Static functions and static objects do not have naming restrictions.
|
||||
|
||||
Acronyms should **NOT** be mixed-case. `http` for the first word in a camel-case name, `HTTP` for all else, but **NEVER** `Http`. This is possibly the only aspect of the controversial nature of code style that I consider indefensibly stupid.
|
||||
|
||||
### API documentation
|
||||
|
||||
(TODO I am writing an API documentation tool; once that becomes stable enough I can talk about documenting libui properly. You'll see vestiges of it throughout ui.h, though.)
|
||||
|
||||
### Other commenting
|
||||
|
||||
(TODO write this part)
|
||||
|
||||
### Compatibility
|
||||
|
||||
libui takes backward compatibility seriously. Your code should not break the current compatibility requirements. All platforms provide a series of macros, defined in the various `uipriv_*.h` files (or `winapi.hpp` on Windows), that specify the minimum required version. If you find yourself needing to remove these or ignore resultant warnings or errors, you're probably breaking compatibility.
|
||||
|
||||
Choosing to drop older versions of Windows, GTK+, and OS X that I could have easily continued to support was not done lightly. If you want to discuss dropping support for an older version of any of these for the benefit of libui, file an issue pleading your case (see below).
|
||||
|
||||
GTK+ versions are harder to drop because I am limited by Linux distribution packaging. In general, I will consider bumping GTK+ versions on a new Ubuntu LTS release, choosing the earliest version available on the major distributions at the time of the *previous* Ubuntu LTS release. As of writing, the next milestone will be *after* April 2018, and the target GTK+ version appears to be 3.18, judging by Ubuntu 16.04 LTS alone. This may be bumped back depending on other distros (or it may not be bumped at all), but you may wish to keep this in mind as you write.
|
||||
|
||||
(TODO talk about future.c/.cpp/.m files)
|
||||
|
||||
As for language compatibility, libui is written in C99. I have no intention of changing this.
|
||||
|
||||
As for build system compatibility, libui uses CMake 3.1.0. If you wish to bump the version, file an issue pleading your case (but see below).
|
||||
|
||||
**If you do plead your case**, keep in mind that "it's old" is not a sufficient reason to drop things. If you can prove that **virtually no one** uses the minimum version anymore, then that is stronger evidence. The best evidence, however, is that not upgrading will hold libui back in some significant way — but beware that there are some things I won't add to libui itself.
|
||||
|
||||
### Windows-specific notes
|
||||
|
||||
The Windows backend of libui is written in C++ using C++11.
|
||||
|
||||
Despite using C++, please refrain from using the following:
|
||||
|
||||
- using C++ in ui_windows.h (this file should still be C compatible)
|
||||
- smart pointers
|
||||
- namespaces
|
||||
- `using namespace`
|
||||
- ATL, MFC, WTL
|
||||
|
||||
The following are not recommended, for consistency with the rest of libui:
|
||||
|
||||
- variable declarations anywhere in a function (keep them all at the top)
|
||||
- `for (int x...` (C++11 foreach syntax is fine, though)
|
||||
- omitting the `struct` on type names for ordinary structs
|
||||
|
||||
The format of a class should be
|
||||
|
||||
```c++
|
||||
class name : public ancestor {
|
||||
int privateVariable;
|
||||
// etc.
|
||||
public:
|
||||
// public stuff here
|
||||
};
|
||||
```
|
||||
|
||||
### GTK+-specific notes
|
||||
|
||||
Avoid GNU-specific language features. I build with strict C99 conformance.
|
||||
|
||||
### OS X-specific notes
|
||||
|
||||
Avoid GNU-specific/clang-specific language features. I build with strict C99 conformance.
|
||||
|
||||
libui is presently **not** ARC-compliant. Features that require ARC should be avoided for now. I may consider changing this in the future, but it will be a significant change.
|
||||
|
||||
To ensure maximum compiler output in the event of a coding error, there should not be any implicit method calls in Objective-C code. For instance, don't do
|
||||
|
||||
```objective-c
|
||||
[[array objectAtIndex:i] method]
|
||||
```
|
||||
|
||||
Instead, cast the result of `objectAtIndex:` to the appropriate type, and then call the method. (TODO learn about, then decide a policy on, soft-generics on things other than `id`)
|
||||
|
||||
The format of a class should be
|
||||
|
||||
```objective-c
|
||||
@interface name : parent<protocols> {
|
||||
// ivars
|
||||
}
|
||||
// properties
|
||||
- (ret)method:(int)arg;
|
||||
// more methods
|
||||
@end
|
||||
|
||||
@implementation name
|
||||
|
||||
- (ret)method:(int)arg
|
||||
{
|
||||
// note the lack of semicolon
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
141
dep/libui/Compatibility.md
Normal file
141
dep/libui/Compatibility.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Useful things in newer versions
|
||||
|
||||
## Windows
|
||||
### Windows 7
|
||||
http://channel9.msdn.com/blogs/pdc2008/pc43
|
||||
|
||||
TODO look up PDC 2008 talk "new shell user interface"
|
||||
|
||||
- new animation and text engine
|
||||
- ribbon control (didn't this have some additional license?)
|
||||
- LVITEM.piColFmt
|
||||
|
||||
### Windows 8
|
||||
|
||||
### Windows 8.1
|
||||
|
||||
### Windows 10
|
||||
|
||||
## GTK+
|
||||
TODO what ships with Ubuntu Quantal (12.10)?
|
||||
|
||||
### GTK+ 3.6
|
||||
ships with: Ubuntu Raring (13.04)
|
||||
|
||||
- GtkEntry and GtkTextView have input purposes and input hints for external input methods but do not change input themselves
|
||||
- according to Company, we connect to insert-text for that
|
||||
- GtkLevelBar
|
||||
- GtkMenuButton
|
||||
- **GtkSearchEntry**
|
||||
|
||||
### GTK+ 3.8
|
||||
ships with: Ubuntu Saucy (13.10)
|
||||
|
||||
Not many interesting new things to us here, unless you count widget-internal tickers and single-click instead of double-click to select list items (a la KDE)... and oh yeah, also widget opacity.
|
||||
|
||||
### GTK+ 3.10
|
||||
ships with: **Ubuntu Trusty (14.04 LTS)**
|
||||
<br>GLib version: 2.40
|
||||
|
||||
- tab character stops in GtkEntry
|
||||
- GtkHeaderBar
|
||||
- intended for titlebar overrides; GtkInfoBar is what I keep thinking GtkHeaderBar is
|
||||
- **GtkListBox**
|
||||
- GtkRevealer for smooth animations of disclosure triangles
|
||||
- GtkSearchBar for custom search popups
|
||||
- **GtkStack and GtkStackSwitcher**
|
||||
- titlebar overrides (seems to be the hot new thing)
|
||||
|
||||
### GTK+ 3.12
|
||||
ships with: Ubuntu Utopic (14.10)
|
||||
<br>GLib version: 2.42
|
||||
|
||||
- GtkActionBar (basically like the bottom-of-the-window toolbars in Mac programs)
|
||||
- gtk_get_locale_direction(), for internationalization
|
||||
- more control over GtkHeaderBar
|
||||
- **GtkPopover**
|
||||
- GtkPopovers on GtkMenuButtons
|
||||
- GtkStack signaling
|
||||
- **gtk_tree_path_new_from_indicesv()** (for when we add Table if we have trees too)
|
||||
|
||||
### GTK+ 3.14
|
||||
ships with: **Debian Jessie**, Ubuntu Vivid (15.04)
|
||||
<br>GLib version: Debian: 2.42, Ubuntu: 2.44
|
||||
|
||||
- gestures
|
||||
- better GtkListbox selection handling
|
||||
- more style classes (TODO also prior?)
|
||||
- delayed switch changes on GtkSwitch
|
||||
|
||||
### GTK+ 3.16
|
||||
ships with: Ubuntu Wily (15.10)
|
||||
<br>GLib version: 2.46
|
||||
|
||||
- gtk_clipboard_get_default() (???)
|
||||
- **GtkGLArea**
|
||||
- proper xalign and yalign for GtkLabel; should get rid of runtime deprecation warnings
|
||||
- better control of GtkListBox model-based creation (probably not relevant but)
|
||||
- GtkModelButton (for GActions; probably not relevant?)
|
||||
- wide handles on GtkPaned
|
||||
- GtkPopoverMenu
|
||||
- IPP paper names in GtkPaperSize (TODO will this be important for printing?)
|
||||
- multiple matches in GtkSearchEntry (TODO evaluate priority)
|
||||
- **GtkStackSidebar**
|
||||
- GTK_STYLE_CLASS_LABEL, GTK_STYLE_CLASS_MONOSPACE, GTK_STYLE_CLASS_STATUSBAR, GTK_STYLE_CLASS_TOUCH_SELECTION, GTK_STYLE_CLASS_WIDE (TODO figure out which of these are useful)
|
||||
- GtkTextView: extend-selection
|
||||
- GtkTextView: font fallbacks
|
||||
|
||||
### GTK+ 3.18
|
||||
|
||||
### GTK+ 3.20
|
||||
|
||||
## Cocoa
|
||||
### Mac OS X 10.8
|
||||
|
||||
- Foundation ([full details](https://developer.apple.com/library/mac/releasenotes/Foundation/RN-FoundationOlderNotes/#//apple_ref/doc/uid/TP40008080-TRANSLATED_CHAPTER_965-TRANSLATED_DEST_999B))
|
||||
- NSDateComponents supports leap months
|
||||
- NSNumberFormatter and NSDateFormatter default to 10.4 behavior by default (need to explicitly do this on 10.7)
|
||||
- **NSUserNotification and NSUserNotificationCenter for Growl-style notifications**
|
||||
- better linguistic triggers for Spanish and Italian
|
||||
- NSByteCountFormatter
|
||||
- AppKit ([full details](https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_8Notes))
|
||||
- view-based NSTableView/NSOutlineView have expansion tooltips
|
||||
- NSScrollView magnification
|
||||
- Quick Look events; TODO see if they conflict with keyboard handling in Area
|
||||
- NSPageController (maybe useful?)
|
||||
- not useful for package UI, but may be useful for a new library (probably not by me): NSSharingService
|
||||
- NSOpenPanel and NSSavePanel are now longer NSPanels or NSWindows in sandboxed applications; this may be an issue should anyone dare to enable sandboxing on a program that uses package ui
|
||||
- NSTextAlternatives
|
||||
- -[NSOpenGLContext setFullScreen] now ineffective
|
||||
- +[NSColor underPageBackgroundColor]
|
||||
|
||||
### Mac OS X 10.9
|
||||
|
||||
- Foundation ([full details](https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/))
|
||||
- system-provided progress reporting/cancellation support
|
||||
- NSURLComponents
|
||||
- **NSCalendar, NSDateFormatter, and NSNumberFormatter are now thread-safe**
|
||||
- various NSCalendar and NSDateComponents improvements
|
||||
- AppKit ([full details](https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/))
|
||||
- sheet handling is now block-based, queued, and in NSWindow; the delegate-based NSApplication API will still exist, except without the queue
|
||||
- similar changes to NSAlert
|
||||
- **return value changes to NSAlert**
|
||||
- window visibility APIs (occlusion)
|
||||
- NSApplicationActivationPolicyAccessory
|
||||
- fullscreen toolbar behavior changes
|
||||
- status items for multiple menu bars
|
||||
- better NSSharingService support
|
||||
- a special accelerated scrolling mode, Responsive Scrolling; won't matter for us since I plan to support the scroll wheel and it won't
|
||||
- NSScrollView live scrolling notifications
|
||||
- NSScrollView floating (anchored/non-scrolling) subviews
|
||||
- better multimonitor support
|
||||
- better key-value observing for NSOpenPanel/NSSavePanel (might want to look this up to see if we can override some other juicy details... TODO)
|
||||
- better accessory view key-view handling in NSOpenPanel/NSSavePanel
|
||||
- NSAppearance
|
||||
- **-[NSTableView moveRowAtIndex:toIndex:] bug regarding first responders fixed**
|
||||
- view-specific RTL overrides
|
||||
|
||||
### Mac OS X 10.10
|
||||
|
||||
### Mac OS X 10.11
|
||||
* **NSLayoutGuide**
|
||||
9
dep/libui/LICENSE
Normal file
9
dep/libui/LICENSE
Normal file
@@ -0,0 +1,9 @@
|
||||
Copyright (c) 2014 Pietro Gagliardi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
(this is called the MIT License or Expat License; see http://www.opensource.org/licenses/MIT)
|
||||
113
dep/libui/NEWS.md
Normal file
113
dep/libui/NEWS.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Old News
|
||||
|
||||
* **27 November 2016**
|
||||
* Decided to split the table stuff into its own branch. It will be developed independently of everything else, along with a few other features.
|
||||
|
||||
* **2 November 2016**
|
||||
* Added two new functions to replace the deleted `uiWindowPosition()` and friends: `uiAreaBeginUserWindowMove()` and `uiAreaBeginUserWindowResize()`. When used in a `uiAreaHandler.Mouse()` event handler, these let you initiate a user-driven mouse move or mouse resize of the window at any point in a uiArea.
|
||||
|
||||
* **31 October 2016**
|
||||
* @krakjoe noticed that I accidentally used thread-unsafe code in uiQueueMain() on Unix. Fixed.
|
||||
|
||||
* **24 October 2016**
|
||||
* `uiWindowSetContentSize()` on Unix no longer needs to call up the GTK+ main loop. As a result, bugs related to strange behavior using that function (and the now-deleted `uiWindowSetPosition()` and `uiWindowCenter()`) should go away. I'll need to go through the bugs to verify as much, though.
|
||||
|
||||
* **22 October 2016**
|
||||
* Due to being unable to guarantee they will work (especially as we move toward capability-driven window systems like Wayland), or being unable to work without hacking that breaks other things, the following functions have been removed: `uiWindowPosition()`, `uiWindowSetPosition()`, `uiWindowCenter()`, and `uiWindowOnPositionChanged()`. Centering may come back at some point in the future, albeit in a possibly restricted form. A function to initiate a user move when a part of a uiArea is clicked will be provided soon.
|
||||
|
||||
* **21 October 2016**
|
||||
* `uiDrawTextWeightUltraBold` is now spelled correctly. Thanks to @krakjoe.
|
||||
|
||||
* **18 June 2016**
|
||||
* Help decide [the design of tables and trees in libui](https://github.com/andlabs/libui/issues/159); the implementation starts within the next few days, if not tomorrow!
|
||||
|
||||
* **17 June 2016**
|
||||
* **CMake 3.1.0 is now required.** This is due to CMake's rapid development pace in the past few years adding things libui needs to build on as many systems as possible. If your OS is supported by libui but its repositories ship with an older version of CMake, you will need to find an updated one somewhere.
|
||||
* Please help [plan out a better menu API](https://github.com/andlabs/libui/issues/152).
|
||||
* `uiMainSteps()` no longer takes any arguments and no longer needs to invoke a function to do the work. You still need to call it, but once you do, it will return immediately and you can then get right to your main loop.
|
||||
* **CMake 3.1.0 is now required.** This is due to CMake's rapid development pace in the past few years adding things libui needs to build on as many systems as possible. If your OS is supported by libui but its repositories ship with an older version of CMake, you will need to find an updated one somewhere.
|
||||
* Added `uiNewVerticalSeparator()` to complement `uiNewHorizontalSeparator()`.
|
||||
|
||||
* **16 June 2016**
|
||||
* Added `uiWindowContentSize()`, `uiWindowSetContentSize()`, and `uiWindowOnContentSizeChanged()` methods for manipulating uiWindow content sizes. Note the use of "content size"; the size you work with does NOT include window decorations (titlebars, menus, etc.).
|
||||
* Added `uiWindowFullscreen()` and `uiWindowSetFullscreen()` to allow making fullscreen uiWindows, taking advantage of OS facilities for fullscreen and without changing the screen resolution (!).
|
||||
* Added `uiWindowBorderless()` and `uiWindowSetBorderless()` for allowing borderless uiWindows.
|
||||
* Added `uiMainSteps()`. You call this instead of `uiMain()` if you want to run the main loop yourself. You pass in a function that will be called; within that function, you call `uiMainStep()` repeatedly until it returns 0, doing whatever you need to do in the meantime. (This was needed because just having `uiMainStep()` by itself only worked on some systems.)
|
||||
* Added `uiProgressBarValue()` and allowed passing -1 to `uiProgressBarSetValue()` to make an indeterminate progress bar. Thanks to @emersion.
|
||||
|
||||
* **15 June 2016**
|
||||
* Added `uiFormDelete()`; thanks to @emersion.
|
||||
* Added `uiWindowPosition()`, `uiWindowSetPosition()`, `uiWindowCenter()`, and `uiWindowOnPositionChanged()`, methods for manipulating uiWindow position.
|
||||
|
||||
* **14 June 2016**
|
||||
* uiDarwinControl now has a `ChildVisibilityChanged()` method and a corresponding `NotifyVisibilityChanged()` function that is called by the default show/hide handlers. This is used to make visibility changes work on OS X; uiBox, uiForm, and uiGrid all respect these now.
|
||||
* The same has been done on the Windows side as well.
|
||||
* Hiding and showing controls and padding calculations are now correct on Windows at long last.
|
||||
* Hiding a control in a uiForm now hides its label on all platforms.
|
||||
|
||||
* **13 June 2016**
|
||||
* `intmax_t` and `uintmax_t` are no longer used for libui API functions; now we use `int`. This should make things much easier for bindings. `int` should be at least 32 bits wide; this should be sufficient for all but the most extreme cases.
|
||||
|
||||
* **12 June 2016**
|
||||
* Added `uiGrid`, a new container control that arranges controls in rows and columns, with stretchy ("expanding") rows, stretchy ("expanding") columns, cells that span rows and columns, and cells whose content is aligned in either direction rather than just filling. It's quite powerful, is it? =P
|
||||
|
||||
* **8 June 2016**
|
||||
* Added `uiForm`, a new container control that arranges controls vertically, with properly aligned labels on each. Have fun!
|
||||
|
||||
* **6 June 2016**
|
||||
* Added `uiRadioButtonsSelected()`, `uiRadioButtonsSetSelected()`, and `uiRadioButtonsOnSelected()` to control selection of a radio button and catch an event when such a thing happens.
|
||||
|
||||
* **5 June 2016**
|
||||
* **Alpha 3.1 is here.** This was a much-needed update to Alpha 3 that changes a few things:
|
||||
* **The build system is now cmake.** cmake 2.8.11 or higher is needed.
|
||||
* Static linking is now fully possible.
|
||||
* MinGW linking is back, but static only.
|
||||
* Added `uiNewPasswordEntry()`, which creates a new `uiEntry` suitable for entering passwords.
|
||||
* Added `uiNewSearchEntry()`, which creates a new `uiEntry` suitable for searching. On some systems, the `OnChanged()` event will be slightly delayed and/or combined, to produce a more natural feel when searching.
|
||||
|
||||
* **29 May 2016**
|
||||
* **Alpha 3 is here!** Get it [here](https://github.com/andlabs/libui/releases/tag/alpha3).
|
||||
* The next packaged release will introduce:
|
||||
* uiGrid, another way to lay out controls, a la GtkGrid
|
||||
* uiOpenGLArea, a way to render OpenGL content in a libui uiArea
|
||||
* uiTable, a data grid control that may or may not have tree facilities (if it does, it will be called uiTree instead)
|
||||
* a complete, possibly rewritten, drawing and text rendering infrastructure
|
||||
* Thanks to @pcwalton, we can now statically link libui! Simply do `make STATIC=1` instead of just `make`.
|
||||
* On Windows you must link both `libui.lib` and `libui.res` AND provide a Common Controls 6 manifest for output static binaries to work properly.
|
||||
|
||||
* **28 May 2016**
|
||||
* As promised, **the minimum system requirements are now OS X 10.8 and GTK+ 3.10 for OS X and Unix, respectively**.
|
||||
|
||||
* **26 May 2016**
|
||||
* Two OS X-specific functions have been added: `uiDarwinMarginAmount()` and `uiDarwinPaddingAmount()`. These return the amount of margins and padding, respectively, to give to a control, and are intended for container implementations. These are suitable for the constant of a NSLayoutConstraint. They both take a pointer parameter that is reserved for future use and should be `NULL`.
|
||||
|
||||
* **25 May 2016**
|
||||
* uiDrawTextLayout attributes are now specified in units of *graphemes* on all platforms. This means characters as seen from a user's perspective, not Unicode codepoints or UTF-8 bytes. So a long string of combining marker codepoints after one codepoint would still count as one grapheme.
|
||||
|
||||
* **24 May 2016**
|
||||
* You can now help choose [a potential new build system for libui](https://github.com/andlabs/libui/issues/62).
|
||||
* Tomorrow I will decide if OS X 10.7 will also be dropped alongside GTK+ 3.4-3.8 this Saturday. Stay tuned.
|
||||
* As promised, `uiCombobox` is now split into `uiCombobox` for non-editable comboboxes and `uiEditableCombobox` for editable comboboxes. Mind the function changes as well :)
|
||||
* There is a new function `uiMainStep()`, which runs one iteration of the main loop. It takes a single boolean argument, indicating whether to wait for an event to occur or not. It returns true if an event was processed (or if no event is available if you don't want to wait) and false if the event loop was told to stop (for instance, `uiQuit()` was called).
|
||||
|
||||
* **23 May 2016**
|
||||
* Fixed surrogate pair drawing on OS X.
|
||||
|
||||
* **22 May 2016**
|
||||
* Two more open questions I'd like your feedback on are available [here](https://github.com/andlabs/libui/issues/48) and [here](https://github.com/andlabs/libui/issues/25).
|
||||
* Sometime in the next 48 hours (before 23:59 EDT on 24 May 2016) I will split `uiCombobox` into two separate controls, `uiCombobox` and `uiEditableCombobox`, each with slightly different events and "selected item" mechanics. Prepare your existing code.
|
||||
* Removed `uiControlVerifyDestroy()`; that is now part of `uiFreeControl()` itself.
|
||||
* Added `uiPi`, a constant for π. This is provided for C and C++ programmers, where there is no standard named constant for π; bindings authors shouldn't need to worry about this.
|
||||
* Fixed uiMultilineEntry not properly having line breaks on Windows.
|
||||
* Added `uiNewNonWrappingMultilineEntry()`, which creates a uiMultilineEntry that scrolls horizontally instead of wrapping lines. (This is not documented as being changeable after the fact on Windows, hence it's a creation-time choice.)
|
||||
* uiAreas on Windows and some internal Direct2D areas now respond to `WM_PRINTCLIENT` properly, which should hopefully increase the quality of screenshots.
|
||||
* uiDateTimePicker on GTK+ works properly on RTL layouts and no longer disappears off the bottom of the screen if not enough room is available. It will also no longer be marked for localization of the time format (what the separator should be and whether to use 24-hour time), as that information is not provided by the locale system. :(
|
||||
* Added `uiUserBugCannotSetParentOnToplevel()`, which should be used by implementations of toplevel controls in their `SetParent()` implementations. This will also be the beginning of consolidating common user bug messages into a single place, though this will be one of the only few exported user bug functions.
|
||||
* uiSpinbox and uiSlider now merely swap their min and max if min ≥ max. They will no longer panic and do nothing, respectively.
|
||||
* Matrix scaling will no longer leave the matrix in an invalid state on OS X and GTK+.
|
||||
* `uiMultilineEntrySetText()` and `uiMutlilineEntryAppend()` on GTK+ no longer fire `OnChanged()` events.
|
||||
|
||||
* **21 May 2016**
|
||||
* I will now post announcements and updates here.
|
||||
* Now that Ubuntu 16.04 LTS is here, no earlier than next Saturday, 28 May 2016 at noon EDT, **I will bump the minimum GTK+ version from 3.4 to 3.10**. This will add a lot of new features that I can now add to libui, such as search-oriented uiEntries, lists of arbitrary control layouts, and more. If you are still running a Linux distribution that doesn't come with 3.10, you will either need to upgrade or use jhbuild to set up a newer version of GTK+ in a private environment.
|
||||
* You can decide if I should also drop OS X 10.7 [here](https://github.com/andlabs/libui/issues/46).
|
||||
195
dep/libui/README.md
Normal file
195
dep/libui/README.md
Normal file
@@ -0,0 +1,195 @@
|
||||
# libui: a portable GUI library for C
|
||||
|
||||
This README is being written.<br>
|
||||
[](https://dev.azure.com/andlabs/libui/_build/latest?definitionId=1&branchName=master)<br>
|
||||
[](https://ci.appveyor.com/project/andlabs/libui/branch/master)
|
||||
|
||||
## Status
|
||||
|
||||
It has come to my attention that I have not been particularly clear about how usable or feature-complete libui is, and that this has fooled many people into expecting more from libui right this moment than I have explicitly promised to make available. I apologize for not doing this sooner.
|
||||
|
||||
libui is currently **mid-alpha** software. Much of what is currently present runs stabily enough for the examples and perhaps some small programs to work, but the stability is still a work-in-progress, much of what is already there is not feature-complete, some of it will be buggy on certain platforms, and there's a lot of stuff missing. In short, here's a list of features that I would like to add to libui, but that aren't in yet:
|
||||
|
||||
- trees
|
||||
- clipboard support, including drag and drop
|
||||
- more and better dialogs
|
||||
- printing
|
||||
- accessibility for uiArea and custom controls
|
||||
- document-based programs
|
||||
- tighter OS integration (especially for document-based programs), to allow programs to fully feel native, rather than merely look and act native
|
||||
- better support for standard dialogs and features (search bars, etc.)
|
||||
- OpenGL support
|
||||
|
||||
In addition, [here](https://github.com/andlabs/libui/issues?utf8=%E2%9C%93&q=master+in%3Atitle+is%3Aissue+is%3Aopen) is a list of issues generalizing existing problems.
|
||||
|
||||
Furthermore, libui is not properly fully documented yet. This is mainly due to the fact that the API was initially unstable enough so as to result in rewriting documentation multiple times, in addition to me not being happy with really any existing C code documentation tool. That being said, I have started to pin down my ideal code documentation style in parts of `ui.h`, most notably in the uiAttributedString APIs. Over time, I plan on extending this to the rest of the headers. You can also use [the documentation for libui's Go bindings](https://godoc.org/github.com/andlabs/ui) as a reference, though it is somewhat stale and not optimally written.
|
||||
|
||||
But libui is not dead; I am working on it whenever I can, and I hope to get it to a point of real quality soon!
|
||||
|
||||
## News
|
||||
|
||||
*Note that today's entry (Eastern Time) may be updated later today.*
|
||||
|
||||
* **7 April 2019**
|
||||
* **The build system has been switched to Meson.** See below for instructions. This change was made because the previous build system, CMake, caused countless headaches over trivial issues. Meson was chosen due to how unproblematic setting up libui's build just right was, as well as having design goals that are by coincidence closely aligned with what libui wants.
|
||||
* Travis CI has been replaced with Azure Pipelines and much of the AppVeyor CI configuration was integrated into the Azure Pipelines configuration. This shouldn't affect most developers.
|
||||
|
||||
* **1 September 2018**
|
||||
* **Alpha 4.1 is here.** This is an emergency fix to Alpha 4 to fix `uiImageAppend()` not working as documented. It now works properly, with one important difference you'll need to care about: **it now requires image data to be alpha-premultiplied**. In addition, `uiImage` also is implemented slightly more nicely now, and `ui.h` has minor documentation typo fixes.
|
||||
* Alpha 4.1 also tries to make everything properly PIC-enabled.
|
||||
|
||||
* **10 August 2018**
|
||||
* **Alpha 4 is finally here.** Everything from Alpha 3.5 and what's listed below is in this release; the two biggest changes are still the new text drawing API and new uiTable control. In between all that is a whole bunch of bugfixes, and hopefully more stability too. Thanks to everybody who helped contribute!
|
||||
* Alpha 4 should hopefully also include automated binary releases via CI. Thanks to those who helped set that up!
|
||||
|
||||
* **8 August 2018**
|
||||
* Finally introduced an API for loading images, `uiImage`, and a new control, `uiTable`, for displaying tabular data. These provide enough basic functionality for now, but will be improved over time. You can read the documentation for the new features as they are [here](https://github.com/andlabs/libui/blob/f47e1423cf95ad7b1001663f3381b5a819fc67b9/uitable.h). Thanks to everyone who helped get to this point, in particular @bcampbell for the initial Windows code, and to everyone else for their patience!
|
||||
|
||||
* **30 May 2018**
|
||||
* Merged the previous Announcements and Updates section of this README into a single News section, and merged the respective archive files into a single NEWS.md file.
|
||||
|
||||
* **16 May 2018**
|
||||
* Thanks to @parro-it and @msink, libui now has better CI, including AppVeyor for Windows CI, and automated creation of binary releases when I make a tagged release.
|
||||
|
||||
* **13 May 2018**
|
||||
* Added new functions to work with uiDateTimePickers: `uiDateTimePickerTime()`, `uiDateTimePickerSetTime()`, and `uiDateTimePickerOnChanged()`. These operate on standard `<time.h>` `struct tm`s. Thanks @cody271!
|
||||
* Release builds on Windows with MSVC should be fixed now; thanks @l0calh05t, @slahn, @mischnic, and @zentner-kyle.
|
||||
|
||||
* **12 May 2018**
|
||||
* GTK+ and OS X now have a cleaner build process for static libraries which no longer has intermediate files and differing configurations. As a result, certain issues should no longer be present. New naming rules for internal symbols of libui have also started being drafted; runtime symbols and edge cases still need to be handled (and the rules applied to Windows) before this can become a regular thing.
|
||||
|
||||
* **2 May 2018**
|
||||
* On Windows, you no longer need to carry around a `libui.res` file with static builds. You do need to link in the appropriate manifest file, such as the one in the `windows/` folder (I still need to figure out exactly what is needed apart from the Common Controls v6 dependency, or at least to create a complete-ish template), or at least include it alongside your executables. This also means you should no longer see random cmake errors when building the static libraries.
|
||||
|
||||
* **18 April 2018**
|
||||
* Introduced a new `uiTimer()` function for running code on a timer on the main thread. (Thanks to @cody271.)
|
||||
* Migrated all code in the `common/` directory to use `uipriv` prefixes for everything that isn't `static`. This is the first step toward fixing static library oddities within libui, allowing libui to truly be safely used as either a static library or a shared library.
|
||||
|
||||
* **18 March 2018**
|
||||
* Introduced an all-new formatted text API that allows you to process formatted text in ways that the old API wouldn't allow. You can read on the whole API [here](https://github.com/andlabs/libui/blob/8944a3fc5528445b9027b1294b6c86bae03eeb89/ui_attrstr.h). There is also a new examples for it: `drawtext`, which shows the whole API at a glance. It doesn't yet support measuring or manipulating text, nor does it currently support functions that would be necessary for things like text editors; all of this will be added back later.
|
||||
* libui also now uses my [utf library](https://github.com/andlabs/utf) for UTF-8 and UTF-16 processing, to allow consistent behavior across platforms. This usage is not completely propagated throughout libui, but the Windows port uses it in most places now, and eventually this will become what libui will use throughout.
|
||||
* Also introduced a formal set of contribution guidelines, see `CONTRIBUTING.md` for details. They are still WIP.
|
||||
|
||||
* **17 February 2018**
|
||||
* The longstanding Enter+Escape crashes on Windows have finally been fixed (thanks to @lxn).
|
||||
* **Alpha 3.5 is now here.** This is a quickie release primiarly intended to deploy the above fix to package ui itself. **It is a partial binary release; sorry!** More new things will come in the next release, which will also introduce semver (so it will be called v0.4.0 instead).
|
||||
* Alpha 3.5 also includes a new control gallery example. The screenshots below have not been updated yet.
|
||||
|
||||
*Old announcements can be found in the NEWS.md file.*
|
||||
|
||||
## Runtime Requirements
|
||||
|
||||
* Windows: Windows Vista SP2 with Platform Update or newer
|
||||
* Unix: GTK+ 3.10 or newer
|
||||
* Mac OS X: OS X 10.8 or newer
|
||||
|
||||
## Build Requirements
|
||||
|
||||
* All platforms:
|
||||
* [Meson](https://mesonbuild.com/) 0.48.0 or newer
|
||||
* Any of Meson's backends; this section assumes you are using [Ninja](https://ninja-build.org/), but there is no reason the other backends shouldn't work.
|
||||
* Windows: either
|
||||
* Microsoft Visual Studio 2013 or newer (2013 is needed for `va_copy()`) — you can build either a static or a shared library
|
||||
* MinGW-w64 (other flavors of MinGW may not work) — **you can only build a static library**; shared library support will be re-added once the following features come in:
|
||||
* [Isolation awareness](https://msdn.microsoft.com/en-us/library/aa375197%28v=vs.85%29.aspx), which is how you get themed controls from a DLL without needing a manifest
|
||||
* Unix: nothing else specific
|
||||
* Mac OS X: nothing else specific, so long as you can build Cocoa programs
|
||||
|
||||
## Building
|
||||
|
||||
libui uses only [the standard Meson build options](https://mesonbuild.com/Builtin-options.html), so a libui build can be set up just like any other:
|
||||
|
||||
```
|
||||
$ # you must be in the top-level libui directory, otherwise this won't work
|
||||
$ meson setup build [options]
|
||||
$ ninja -C build
|
||||
```
|
||||
|
||||
Once this completes, everything will be under `build/meson-out/`. (Note that unlike the previous build processes, everything is built by default, including tests and examples.)
|
||||
|
||||
The most important options are:
|
||||
|
||||
* `--buildtype=(debug|release|...)` controls the type of build made; the default is `debug`. For a full list of valid values, consult [the Meson documentation](https://mesonbuild.com/Running-Meson.html).
|
||||
* `--default-library=(shared|static)` controls whether libui is built as a shared library or a static library; the default is `shared`. You currently cannot specify `both`, as the build process changes depending on the target type (though I am willing to look into changing things if at all possible).
|
||||
* `-Db_sanitize=which` allows enabling the chosen [sanitizer](https://github.com/google/sanitizers) on a system that supports sanitizers. The list of supported values is in [the Meson documentation](https://mesonbuild.com/Builtin-options.html#base-options).
|
||||
* `--backend=backend` allows using the specified `backend` for builds instead of `ninja` (the default). A list of supported values is in [the Meson documentation](https://mesonbuild.com/Builtin-options.html#universal-options).
|
||||
|
||||
Most other built-in options will work, though keep in mind there are a handful of options that cannot be overridden because libui depends on them holding a specific value; if you do override these, though, libui will warn you when you run `meson`.
|
||||
|
||||
The Meson website and documentation has more in-depth usage instructions.
|
||||
|
||||
For the sake of completeness, I should note that the default value of `--layout` is `flat`, not the usual `mirror`. This is done both to make creating the release archives easier as well as to reduce the chance that shared library builds will fail to start on Windows because the DLL is in another directory. You can always specify this manually if you want.
|
||||
|
||||
Backends other than `ninja` should work, but are untested by me.
|
||||
|
||||
## Installation
|
||||
|
||||
Meson also supports installing from source; if you use Ninja, just do
|
||||
|
||||
```
|
||||
$ ninja -C build install
|
||||
```
|
||||
|
||||
When running `meson`, the `--prefix` option will set the installation prefix. [The Meson documentation](https://mesonbuild.com/Builtin-options.html#universal-options) has more information, and even lists more fine-grained options that you can use to control the installation.
|
||||
|
||||
#### Arch Linux
|
||||
|
||||
Can be built from AUR: https://aur.archlinux.org/packages/libui-git/
|
||||
|
||||
## Documentation
|
||||
|
||||
Needs to be written. Consult `ui.h` and the examples for details for now.
|
||||
|
||||
## Language Bindings
|
||||
|
||||
libui was originally written as part of my [package ui for Go](https://github.com/andlabs/ui). Now that libui is separate, package ui has become a binding to libui. As such, package ui is the only official binding.
|
||||
|
||||
Other people have made bindings to other languages:
|
||||
|
||||
Language | Bindings
|
||||
--- | ---
|
||||
C++ | [libui-cpp](https://github.com/billyquith/libui-cpp), [cpp-libui-qtlike](https://github.com/aoloe/cpp-libui-qtlike)
|
||||
C# / .NET Framework | [LibUI.Binding](https://github.com/NattyNarwhal/LibUI.Binding)
|
||||
C# / .NET Core | [DevZH.UI](https://github.com/noliar/DevZH.UI), [SharpUI](https://github.com/benpye/sharpui/), [TCD.UI](https://github.com/tacdevel/tcdfx)
|
||||
CHICKEN Scheme | [wasamasa/libui](https://github.com/wasamasa/libui)
|
||||
Common Lisp | [jinwoo/cl-ui](https://github.com/jinwoo/cl-ui)
|
||||
Crystal | [libui.cr](https://github.com/Fusion/libui.cr), [hedron](https://github.com/Qwerp-Derp/hedron)
|
||||
D | [DerelictLibui (flat API)](https://github.com/Extrawurst/DerelictLibui), [libuid (object-oriented)](https://github.com/mogud/libuid)
|
||||
Euphoria | [libui-euphoria](https://github.com/ghaberek/libui-euphoria)
|
||||
Harbour | [hbui](https://github.com/rjopek/hbui)
|
||||
Haskell | [haskell-libui](https://github.com/beijaflor-io/haskell-libui)
|
||||
JavaScript/Node.js | [libui-node](https://github.com/parro-it/libui-node), [libui.js (merged into libui-node?)](https://github.com/mavenave/libui.js), [proton-native](https://github.com/kusti8/proton-native), [vuido](https://github.com/mimecorg/vuido)
|
||||
Julia | [Libui.jl](https://github.com/joa-quim/Libui.jl)
|
||||
Kotlin | [kotlin-libui](https://github.com/msink/kotlin-libui)
|
||||
Lua | [libuilua](https://github.com/zevv/libuilua), [libui-lua](https://github.com/mdombroski/libui-lua), [lui](http://tset.de/lui/index.html), [lui](https://github.com/zhaozg/lui)
|
||||
Nim | [ui](https://github.com/nim-lang/ui)
|
||||
Perl6 | [perl6-libui](https://github.com/Garland-g/perl6-libui)
|
||||
PHP | [ui](https://github.com/krakjoe/ui)
|
||||
Python | [pylibui](https://github.com/joaoventura/pylibui)
|
||||
Ruby | [libui-ruby](https://github.com/jamescook/libui-ruby), [libui](https://github.com/kojix2/libui)
|
||||
Rust | [libui-rs](https://github.com/rust-native-ui/libui-rs)
|
||||
Scala | [scalaui](https://github.com/lolgab/scalaui)
|
||||
Swift | [libui-swift](https://github.com/sclukey/libui-swift)
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Why does my program start in the background on OS X if I run from the command line?
|
||||
OS X normally does not start program executables directly; instead, it uses [Launch Services](https://developer.apple.com/reference/coreservices/1658613-launch_services?language=objc) to coordinate the launching of the program between the various parts of the system and the loading of info from an .app bundle. One of these coordination tasks is responsible for bringing a newly launched app into the foreground. This is called "activation".
|
||||
|
||||
When you run a binary directly from the Terminal, however, you are running it directly, not through Launch Services. Therefore, the program starts in the background, because no one told it to activate! Now, it turns out [there is an API](https://developer.apple.com/reference/appkit/nsapplication/1428468-activateignoringotherapps) that we can use to force our app to be activated. But if we use it, then we'd be trampling over Launch Services, which already knows whether it should activate or not. Therefore, libui does not step over Launch Services, at the cost of requiring an extra user step if running directly from the command line.
|
||||
|
||||
See also [this](https://github.com/andlabs/libui/pull/20#issuecomment-211381971) and [this](http://stackoverflow.com/questions/25318524/what-exactly-should-i-pass-to-nsapp-activateignoringotherapps-to-get-my-appl).
|
||||
|
||||
## Contributing
|
||||
|
||||
See `CONTRIBUTING.md`.
|
||||
|
||||
## Screenshots
|
||||
|
||||
From examples/controlgallery:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
262
dep/libui/TODO.md
Normal file
262
dep/libui/TODO.md
Normal file
@@ -0,0 +1,262 @@
|
||||
- make sure the last line of text layouts include leading
|
||||
|
||||
- documentation notes:
|
||||
- static binaries do not link system libraries, meaning apps still depend on shared GTK+, etc.
|
||||
- ui*Buttons are NOT compatible with uiButton functions
|
||||
|
||||
- more robust layout handling
|
||||
- uiFormTie() for ensuring multiple uiForms have the same label area widths
|
||||
- uiSizeGroup for size groups (GtkSizeGroup on GTK+, auto layout constraints on OS X; consider adding after 10.8 is gone)
|
||||
|
||||
- windows: should the initial hwndInsertAfter be HWND_BOTTOM for what we want?
|
||||
|
||||
- windows: document the rules for controls and containers
|
||||
|
||||
- windows: document the minimum size change propagation system
|
||||
|
||||
- provisions for controls that cannot be grown? especiailly for windows
|
||||
|
||||
- LC_VERSION_MIN_MACOSX has the 10.11 SDK; see if we can knock it down to 10.8 too; OS version is fine
|
||||
- apply the OS version stuff to the test program and examples too
|
||||
- what about micro versions (10.8.x)? force 10.8.0?
|
||||
|
||||
- go through ALL the objective-c objects we create and make sure we are using the proper ownership (alloc/init and new are owned by us, all class method constructors are autoreleased - thanks mikeash)
|
||||
|
||||
- on OS X, edit shortcuts like command-C working require that the menu entries be defined, or so it seems, even for NSAlert
|
||||
- other platforms?
|
||||
|
||||
- make sure all OS X event handlers that use target-action set the action to NULL when the target is unset
|
||||
|
||||
- provide a way to get the currently selected uiTab page? set?
|
||||
|
||||
- make it so that the windows cntrols only register a resize if their new minimum size is larger than their current size to easen the effect of flicker
|
||||
- it won't remove that outright, but it'll help
|
||||
|
||||
- add an option to the test program to run page7b as an independent test in its own window
|
||||
- same for page7c
|
||||
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/01/12/57833.aspx provide a DEF file on Windows
|
||||
|
||||
- all ports: update state when adding a control to a parent
|
||||
- should uiWindowsSizing be computed against the window handle, not the parent?
|
||||
|
||||
- DPI awareness on windows
|
||||
|
||||
- http://stackoverflow.com/questions/4543087/applicationwillterminate-and-the-dock-but-wanting-to-cancel-this-action
|
||||
|
||||
ultimately:
|
||||
- MAYBE readd lifetime handling/destruction blocking
|
||||
- related? [12:25] <ZeroOne> And the blue outline on those buttons [ALL clicked buttons on Windows 7] won't go away
|
||||
- I get this too
|
||||
- not anymore
|
||||
- SWP_NOCOPYBITS to avoid button redraw issues on Windows when not in tab, but only when making resize faster
|
||||
- secondary side alignment control in uiBox
|
||||
- Windows: don't abort if a cleanup function fails?
|
||||
|
||||
- 32-bit Mac OS X support (requires lots of code changes)
|
||||
- change the build system to be more receptive to arch changes
|
||||
|
||||
notes to self
|
||||
- explicitly document label position at top-left corner
|
||||
- explicitly document that if number of radio buttons >= 1 there will always be a selection
|
||||
- mark that uiControlShow() on a uiWindow() will bring to front and give keyboard focus because of OS X
|
||||
- make sure ShowWindow() is sufficient for zorder on Windows
|
||||
- document that you CAN use InsertAt functions to insert at the first invalid index, even if the array is empty
|
||||
- note that uiTabInsertAt() does NOT change the current tab page (it may change its index if inserting before the current page)
|
||||
- note that the default action for uiWindowOnClosing() is to return 0 (keep the window open)
|
||||
- note that uiInitOptions should be initialized to zero
|
||||
- explicitly document that uiCheckboxSetChecked() and uiEntrySetText() do not fire uiCheckboxOnToggled() and uiEntryOnChanged(), respectively
|
||||
- note that if a menu is requested on systems with menubars on windows but no menus are defined, the result is a blank menubar, with whatever that means left up to the OS to decide
|
||||
- note that handling of multiple consecutive separators in menus, leading separators in menus, and trailing separators in menus are all OS-defined
|
||||
- note that uiDrawMatrixInvert() does not change the matrix if it fails
|
||||
- note that the use of strings that are not strictly valid UTF-8 results in undefined behavior
|
||||
|
||||
- test RTL
|
||||
- automate RTL
|
||||
- now that stock items are deprecated, I have to maintain translations of the Cancel, Open, and Save buttons on GTK+ myself (thanks baedert in irc.gimp.net/#gtk+)
|
||||
- either that or keep using stock items
|
||||
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2014/02/26/10503148.aspx
|
||||
|
||||
- build optimizations
|
||||
|
||||
- use http://www.appveyor.com/ to do Windows build CI since people want CI
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- consider just having the windows backend in C++
|
||||
- consider having it all in C++
|
||||
|
||||
|
||||
|
||||
don't forget LONGTERMs as well
|
||||
|
||||
notes
|
||||
- http://blogs.msdn.com/b/oldnewthing/archive/2004/03/29/101121.aspx on accelerators
|
||||
|
||||
- group and tab should act as if they have no child if the child is hidden
|
||||
on windows
|
||||
|
||||
|
||||
|
||||
- a way to do recursive main loops
|
||||
- how do we handle 0 returns from non-recursive uiMainStep() calls that aren't the main loop? (event handlers, for instance)
|
||||
- should repeated calls to uiMainStep() after uiQuit() return 0 reliably? this will be needed for non-recursive loops
|
||||
|
||||
http://stackoverflow.com/questions/38338426/meaning-of-ampersand-in-rc-files/38338841?noredirect=1#comment64093084_38338841
|
||||
|
||||
label shortcut keys
|
||||
|
||||
- remove whining from source code
|
||||
|
||||
[01:41:47] <vrishab> Hi. does pango support "fgalpha". I see that foreground="112233xx" works ( alpha=xx ), but fgalpha is a no-op
|
||||
[01:52:29] <vrishab> pango_attr_foreground_alpha_new (32767) seems to be called in either case, but only the "foreground" attr works
|
||||
[01:56:09] lolek (lolek@ip-91-244-230-76.simant.pl) joined the channel
|
||||
[01:57:48] <vrishab> ok. seems like "foreground" is mandatory attr, 1. "foreground-without-alpha" + "alpha" works 2. "foreground-with-alpha" works. 3. "alpha" alone doesn
|
||||
[01:57:52] <vrishab> 't work
|
||||
[01:58:29] <vrishab> Is there a way to just specify alpha on the current foreground color ?
|
||||
[02:00:23] lolek (lolek@ip-91-244-230-76.simant.pl) left the channel
|
||||
[02:07:41] mjog (mjog@uniwide-pat-pool-129-94-8-98.gw.unsw.edu.au) left IRC (Quit: mjog)
|
||||
[02:08:10] seb128 (seb128@53542B83.cm-6-5a.dynamic.ziggo.nl) joined the channel
|
||||
[02:12:37] <andlabs> huh
|
||||
[02:12:41] <andlabs> what version of pango?
|
||||
[02:13:05] <vrishab> the latest .
|
||||
[02:15:00] <vrishab> 1.40.3
|
||||
[02:20:46] <andlabs> I'll ahve to keep this in mind then, thanks
|
||||
[02:20:59] <andlabs> if only there was a cairo-specific attribute for alpha...
|
||||
|
||||
FONT LOADING
|
||||
|
||||
[00:10:08] <hergertme> andlabs: is there API yet to load from memory? last i checked i only found from file (which we use in builder). https://git.gnome.org/browse/gnome-builder/tree/libide/editor/ide-editor-map-bin.c#n115
|
||||
[00:13:12] mrmcq2u_ (mrmcq2u@109.79.53.90) joined the channel
|
||||
[00:14:59] mrmcq2u (mrmcq2u@109.79.73.102) left IRC (Ping timeout: 181 seconds)
|
||||
[00:15:19] <andlabs> hergertme: no, which is why I was asking =P
|
||||
[00:15:30] <andlabs> I would have dug down if I could ensure at least something about the backends a GTK+ 3 program uses
|
||||
[00:15:39] <andlabs> on all platforms except windows and os x
|
||||
[00:16:11] <hergertme> to the best of my (partially outdated, given pace of foss) knowledge there isn't an api to load from memory
|
||||
[00:16:28] <hergertme> you can possibly make a tmpdir and put a temp file in there
|
||||
[00:16:52] <hergertme> and load that as your font dir in your FcConfig, so any PangoFontDescription would point to that one font, no matter what
|
||||
[00:17:18] <hergertme> (using the API layed out in that link)
|
||||
[00:18:18] dsr1014__ (dsr1014@c-73-72-102-18.hsd1.il.comcast.net) joined the channel
|
||||
[00:35:18] simukis_ (simukis@78-60-58-6.static.zebra.lt) left IRC (Quit: simukis_)
|
||||
[00:35:48] dreamon_ (dreamon@ppp-188-174-49-41.dynamic.mnet-online.de) joined the channel
|
||||
[00:40:09] samtoday_ (samtoday@114-198-116-132.dyn.iinet.net.au) joined the channel
|
||||
[00:40:32] mjog (mjog@120.18.225.46) joined the channel
|
||||
[00:40:38] <andlabs> hergertme: not necessarily fontconfig
|
||||
[00:40:45] <andlabs> it can be with ft2 or xft I guess
|
||||
[00:40:55] <andlabs> especially since I want the API NOT to make the font part of the font panel
|
||||
[00:42:07] <hergertme> what sort of deprecated code are you trying to support?
|
||||
[00:42:35] <hergertme> both of those are deprecated in pango fwiw
|
||||
[00:43:06] <hergertme> on Linux im pretty sure we use FC everywhere these days
|
||||
[00:44:46] <hergertme> (and gtk_widget_set_font_map() is how you get your custom font into a widget without affecting the global font lists, as layed out in that link)
|
||||
[00:49:14] vasaikar (vasaikar@125.16.97.121) joined the channel
|
||||
[00:50:14] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Client exited)
|
||||
[00:50:49] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) joined the channel
|
||||
[00:51:43] PioneerAxon (PioneerAxo@122.171.61.146) left IRC (Ping timeout: 180 seconds)
|
||||
[00:57:47] PioneerAxon (PioneerAxo@106.201.37.181) joined the channel
|
||||
[01:03:01] karlt (karl@2400:e780:801:224:f121:e611:d139:e70e) left IRC (Ping timeout: 181 seconds)
|
||||
[01:05:49] muhannad (muhannad@95.218.26.152) left IRC (Quit: muhannad)
|
||||
[01:07:51] <andlabs> hergertme: hm
|
||||
[01:07:54] <andlabs> all right, thanks
|
||||
[01:08:05] <andlabs> hergertme: fwiw right now my requirement is 3.10
|
||||
[01:10:47] <hergertme> ah, well you'll probably be missing the neccesary font API on gtk_widget
|
||||
[01:11:04] <hergertme> but pango should be fine even back as far as https://developer.gnome.org/pango/1.28/PangoFcFontMap.html
|
||||
[01:11:56] <andlabs> good
|
||||
[01:12:04] <andlabs> because this is for custom drawing into a DrawingArea
|
||||
[01:14:12] <hergertme> presumably just create your PangoContext as normal, but call pango_context_set_font_map() with the map you've setup. now, the load a font from a file i dont think was added to FontConfig until later though (not sure what release)
|
||||
[01:15:53] <hergertme> FcConfigAppFontAddFile() <-- that API
|
||||
[01:16:30] <hergertme> great, and they don't say what version the API was added in teh docs
|
||||
function: ide_editor_map_bin_add()
|
||||
|
||||
- Mouse ClickLock: do we need to do anything special? *should* we? https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx
|
||||
- consider a uiAnticipateDoubleClick() or uiDoubleClickTime() (for a uiQueueTimer()) or something: https://blogs.msdn.microsoft.com/oldnewthing/20041015-00/?p=37553
|
||||
|
||||
- determine whether MSGF_USER is for and if it's correct for our uiArea message filter (if we have one)
|
||||
|
||||
- source file encoding and MSVC compiler itself? https://stackoverflow.com/questions/20518040/how-can-i-get-the-directwrite-padwrite-sample-to-work
|
||||
- also need to worry about object file and output encoding...
|
||||
- this also names the author of the padwrite sample
|
||||
|
||||
- OpenType features TODOs
|
||||
- https://stackoverflow.com/questions/32545675/what-are-the-default-typography-settings-used-by-idwritetextlayout
|
||||
- feature/shaping interaction rules for arabic: https://www.microsoft.com/typography/OpenTypeDev/arabic/intro.htm
|
||||
- other stuff, mostly about UIs and what users expect to be able to set
|
||||
- https://klim.co.nz/blog/towards-an-ideal-opentype-user-interface/
|
||||
- https://libregraphicsmeeting.org/2016/designing-for-many-applications-opentype-features-ui/
|
||||
- https://www.youtube.com/watch?v=wEyDhsH076Y
|
||||
- https://twitter.com/peter_works
|
||||
- http://ilovetypography.com/2014/10/22/better-ui-for-better-typography-adobe-petition/
|
||||
- http://silgraphite.sourceforge.net/ui/studynote.html
|
||||
|
||||
- add NXCOMPAT (DEP awareness) to the Windows builds
|
||||
- and ASLR too? or is that not a linker setting
|
||||
|
||||
OS X: embedding an Info.plist into a binary directly
|
||||
https://www.objc.io/issues/6-build-tools/mach-o-executables/
|
||||
TODO will this let Dictation work?
|
||||
TODO investigate ad-hoc codesigning
|
||||
|
||||
https://blogs.msdn.microsoft.com/oldnewthing/20040112-00/?p=41083 def files for decoration (I forget if I said this earlier)
|
||||
|
||||
TODO ClipCursor() stuff; probably not useful for libui but still
|
||||
https://blogs.msdn.microsoft.com/oldnewthing/20140102-00/?p=2183
|
||||
https://blogs.msdn.microsoft.com/oldnewthing/20061117-03/?p=28973
|
||||
https://msdn.microsoft.com/en-us/library/windows/desktop/ms648383(v=vs.85).aspx
|
||||
|
||||
https://cmake.org/Wiki/CMake_Useful_Variables
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
|
||||
On Unix systems, this will make linker report any unresolved symbols from object files (which is quite typical when you compile many targets in CMake projects, but do not bother with linking target dependencies in proper order).
|
||||
(I used to have something like this back when I used makefiles; did it convert in? I forget)
|
||||
|
||||
look into these for the os x port
|
||||
https://developer.apple.com/documentation/appkit/view_management/nseditor?language=objc
|
||||
https://developer.apple.com/documentation/appkit/view_management/nseditorregistration?language=objc
|
||||
|
||||
for future versions of the os x port
|
||||
https://developer.apple.com/documentation/appkit/nslayoutguide?language=objc and anchors
|
||||
https://developer.apple.com/documentation/appkit/nsuserinterfacecompression?language=objc https://developer.apple.com/documentation/appkit/nsuserinterfacecompressionoptions?language=objc
|
||||
though at some point we'll be able to use NSStackView and NSGridView directly, so...
|
||||
|
||||
Cocoa PDFs
|
||||
https://developer.apple.com/documentation/appkit/nspdfimagerep?language=objc
|
||||
https://developer.apple.com/documentation/coregraphics?language=objc
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_pagination/osxp_pagination.html#//apple_ref/doc/uid/20001051-119037
|
||||
https://developer.apple.com/documentation/appkit/nsprintoperation/1529269-pdfoperationwithview?language=objc
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printapps/osxp_printapps.html#//apple_ref/doc/uid/20000861-BAJBFGED
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printingapi/osxp_printingapi.html#//apple_ref/doc/uid/10000083i-CH2-SW2
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printinfo/osxp_printinfo.html#//apple_ref/doc/uid/20000864-BAJBFGED
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_printlayoutpanel/osxp_printlayoutpanel.html#//apple_ref/doc/uid/20000863-BAJBFGED
|
||||
https://developer.apple.com/documentation/appkit/nspagelayout?language=objc
|
||||
https://developer.apple.com/documentation/appkit/nsprintinfo?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/core_printing?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1463247-pmcreatesession?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/pmprintsession?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1463416-pmsessionbeginpagenodialog?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1506831-anonymous/kpmdestinationprocesspdf?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1461960-pmcreategenericprinter?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1460101-pmsessionbegincgdocumentnodialog?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1464527-pmsessionenddocumentnodialog?language=objc
|
||||
https://developer.apple.com/documentation/applicationservices/1461952-pmsessiongetcggraphicscontext?language=objc
|
||||
https://developer.apple.com/library/content/technotes/tn2248/_index.html
|
||||
https://developer.apple.com/library/content/samplecode/PMPrinterPrintWithFile/Introduction/Intro.html
|
||||
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Printing/osxp_aboutprinting/osxp_aboutprt.html
|
||||
|
||||
- run os x code with `OBJC_DEBUG_MISSING_POOLS=YES` and other `OBJC_HELP=YES` options
|
||||
- turn off the autorelease pool to make sure we're not autoreleasing improperly
|
||||
|
||||
TODO investigate -Weverything in clang alongside -Wall in MSVC (and in gcc too maybe...)
|
||||
|
||||
mac os x accessibility
|
||||
- https://developer.apple.com/documentation/appkit/nsworkspace/1524656-accessibilitydisplayshoulddiffer?language=objc
|
||||
- https://developer.apple.com/documentation/appkit/nsworkspace/1526290-accessibilitydisplayshouldincrea?language=objc
|
||||
- https://developer.apple.com/documentation/appkit/nsworkspace/1533006-accessibilitydisplayshouldreduce?language=objc
|
||||
|
||||
uiEntry disabling bugs http://www.cocoabuilder.com/archive/cocoa/215525-nstextfield-bug-can-be.html
|
||||
uiMultilineEntry disabling https://developer.apple.com/library/content/qa/qa1461/_index.html
|
||||
|
||||
more TODOs:
|
||||
- make no guarantee about buildability of feature branches
|
||||
3
dep/libui/UPSTREAM.md
Normal file
3
dep/libui/UPSTREAM.md
Normal file
@@ -0,0 +1,3 @@
|
||||
This is a clone of the master branch of https://github.com/andlabs/libui made
|
||||
on 2021-12-05.
|
||||
|
||||
378
dep/libui/azure-pipelines.yml
Normal file
378
dep/libui/azure-pipelines.yml
Normal file
@@ -0,0 +1,378 @@
|
||||
# 31 march 2019
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- '*'
|
||||
tags:
|
||||
include:
|
||||
- '*'
|
||||
|
||||
variables:
|
||||
releaseExamples: 'controlgallery cpp-multithread datetime drawtext histogram tester timer'
|
||||
|
||||
jobs:
|
||||
|
||||
# linux {
|
||||
|
||||
- job: linux_386_shared
|
||||
displayName: 'Linux 386 Shared'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/linux-386-install-gtk-dev.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: 386
|
||||
libtype: shared
|
||||
libfiles: libui.so.0
|
||||
osHeader: ui_unix.h
|
||||
|
||||
- job: linux_386_static
|
||||
displayName: 'Linux 386 Static'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/linux-386-install-gtk-dev.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: 386
|
||||
libtype: static
|
||||
libfiles: libui.a
|
||||
osHeader: ui_unix.h
|
||||
|
||||
- job: linux_amd64_shared
|
||||
displayName: 'Linux amd64 Shared'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/linux-install-gtk-dev.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: amd64
|
||||
libtype: shared
|
||||
libfiles: libui.so.0
|
||||
osHeader: ui_unix.h
|
||||
|
||||
- job: linux_amd64_static
|
||||
displayName: 'Linux amd64 Static'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/linux-install-gtk-dev.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: amd64
|
||||
libtype: static
|
||||
libfiles: libui.a
|
||||
osHeader: ui_unix.h
|
||||
|
||||
# }
|
||||
|
||||
# windows vs2015 {
|
||||
|
||||
- job: windows_386_msvc2015_shared
|
||||
displayName: 'Windows 386 MSVC2015 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/vs2015-install-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2015
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
- job: windows_386_msvc2015_static
|
||||
displayName: 'Windows 386 MSVC2015 Static'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/vs2015-install-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2015
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
- job: windows_amd64_msvc2015_shared
|
||||
displayName: 'Windows amd64 MSVC2015 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/vs2015-install-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2015
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
- job: windows_amd64_msvc2015_static
|
||||
displayName: 'Windows amd64 MSVC2015 Static'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/vs2015-install-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2015
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
# }
|
||||
|
||||
# windows vs2017 {
|
||||
|
||||
- job: windows_386_msvc2017_shared
|
||||
displayName: 'Windows 386 MSVC2017 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2017
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
- job: windows_386_msvc2017_static
|
||||
displayName: 'Windows 386 MSVC2017 Static'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2017
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
- job: windows_amd64_msvc2017_shared
|
||||
displayName: 'Windows amd64 MSVC2017 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2017
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
- job: windows_amd64_msvc2017_static
|
||||
displayName: 'Windows amd64 MSVC2017 Static'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2017
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
|
||||
# }
|
||||
|
||||
# mac {
|
||||
|
||||
# TODO beforeConfigure/beforeBuild: export SDKROOT=$(xcodebuild -version -sdk macosx10.13 Path)?
|
||||
|
||||
- job: darwin_amd64_shared
|
||||
displayName: 'Darwin amd64 Shared'
|
||||
pool:
|
||||
vmImage: 'macos-10.13'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/darwin-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
libtype: shared
|
||||
libfiles: libui.A.dylib
|
||||
osHeader: ui_darwin.h
|
||||
|
||||
- job: darwin_amd64_static
|
||||
displayName: 'Darwin amd64 Static'
|
||||
pool:
|
||||
vmImage: 'macos-10.13'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: azure-pipelines/darwin-install-ninja.yml
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
libtype: static
|
||||
libfiles: libui.a
|
||||
osHeader: ui_darwin.h
|
||||
|
||||
# }
|
||||
399
dep/libui/azure-pipelines/TODOMatrix
Normal file
399
dep/libui/azure-pipelines/TODOMatrix
Normal file
@@ -0,0 +1,399 @@
|
||||
# 31 march 2019
|
||||
|
||||
variables:
|
||||
releaseExamples: 'controlgallery cpp-multithread datetime drawtext histogram tester timer'
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
# targetname:
|
||||
# os: 'fill this in'
|
||||
# arch: 'fill this in'
|
||||
# toolchain: 'fill this in'
|
||||
# libtype: 'fill this in'
|
||||
# vmImage: 'fill this in'
|
||||
# python3Template: 'fill filename'
|
||||
# depsAndNinjaTemplate: 'fill filename'
|
||||
# beforeConfigure: 'fill this in'
|
||||
# beforeBuild: 'fill this in'
|
||||
# afterBuild: 'fill this in'
|
||||
# artifactTemplate: 'fill filename'
|
||||
# libfiles: 'fill this in'
|
||||
# osHeader: 'fill this in'
|
||||
linux_386_shared:
|
||||
os: 'linux'
|
||||
arch: '386'
|
||||
libtype: 'shared'
|
||||
vmImage: 'ubuntu-16.04'
|
||||
python3Template: 'azure-pipelines/setup-python3.yml'
|
||||
depsAndNinjaTemplate: 'azure-pipelines/linux-386-install-gtk-dev.yml'
|
||||
beforeConfigure: 'export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig'
|
||||
artifactTemplate: 'azure-pipelines/artifacts.yml'
|
||||
libfiles: 'libui.so.0'
|
||||
osHeader: 'ui_unix.h'
|
||||
|
||||
pool:
|
||||
vmImage: $(vmImage)
|
||||
|
||||
workspace:
|
||||
clean: all
|
||||
|
||||
steps:
|
||||
- template: $(variables.python3Template)
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
- template: $(variables.depsAndNinjaTemplate)
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: $(beforeConfigure)
|
||||
defaultLibrary: $(libtype)
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: $(beforeBuild)
|
||||
afterBuild: $(afterBuild)
|
||||
- template: $(variables.artifactTemplate)
|
||||
parameters:
|
||||
os: $(os)
|
||||
arch: $(arch)
|
||||
toolchain: $(toolchain)
|
||||
libtype: $(libtype)
|
||||
libfiles: $(libfiles)
|
||||
osHeader: $(osHeader)
|
||||
|
||||
## linux {
|
||||
#- job: linux_386_static
|
||||
# displayName: 'Linux 386 Static'
|
||||
# pool:
|
||||
# vmImage: 'ubuntu-16.04'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/linux-386-install-gtk-dev.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# - template: azure-pipelines/artifacts.yml
|
||||
# parameters:
|
||||
# os: linux
|
||||
# arch: 386
|
||||
# libtype: static
|
||||
# libfiles: libui.a
|
||||
# osHeader: ui_unix.h
|
||||
#
|
||||
#- job: linux_amd64_shared
|
||||
# displayName: 'Linux amd64 Shared'
|
||||
# pool:
|
||||
# vmImage: 'ubuntu-16.04'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/linux-install-gtk-dev.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# defaultLibrary: shared
|
||||
# - template: azure-pipelines/build.yml
|
||||
# - template: azure-pipelines/artifacts.yml
|
||||
# parameters:
|
||||
# os: linux
|
||||
# arch: amd64
|
||||
# libtype: shared
|
||||
# libfiles: libui.so.0
|
||||
# osHeader: ui_unix.h
|
||||
#
|
||||
#- job: linux_amd64_static
|
||||
# displayName: 'Linux amd64 Static'
|
||||
# pool:
|
||||
# vmImage: 'ubuntu-16.04'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/linux-install-gtk-dev.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# - template: azure-pipelines/artifacts.yml
|
||||
# parameters:
|
||||
# os: linux
|
||||
# arch: amd64
|
||||
# libtype: static
|
||||
# libfiles: libui.a
|
||||
# osHeader: ui_unix.h
|
||||
#
|
||||
## }
|
||||
#
|
||||
## windows vs2015 {
|
||||
#
|
||||
#- job: windows_386_msvc2015_shared
|
||||
# displayName: 'Windows 386 MSVC2015 Shared'
|
||||
# pool:
|
||||
# vmImage: 'vs2015-win2012r2'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/vs2015-install-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
# defaultLibrary: shared
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: 386
|
||||
# toolchain: msvc2015
|
||||
# libtype: shared
|
||||
# libfiles: libui.dll libui.exp libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
#- job: windows_386_msvc2015_static
|
||||
# displayName: 'Windows 386 MSVC2015 Static'
|
||||
# pool:
|
||||
# vmImage: 'vs2015-win2012r2'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/vs2015-install-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
# afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: 386
|
||||
# toolchain: msvc2015
|
||||
# libtype: static
|
||||
# libfiles: libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
#- job: windows_amd64_msvc2015_shared
|
||||
# displayName: 'Windows amd64 MSVC2015 Shared'
|
||||
# pool:
|
||||
# vmImage: 'vs2015-win2012r2'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/vs2015-install-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
# defaultLibrary: shared
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: amd64
|
||||
# toolchain: msvc2015
|
||||
# libtype: shared
|
||||
# libfiles: libui.dll libui.exp libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
#- job: windows_amd64_msvc2015_static
|
||||
# displayName: 'Windows amd64 MSVC2015 Static'
|
||||
# pool:
|
||||
# vmImage: 'vs2015-win2012r2'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/vs2015-install-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
# afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: amd64
|
||||
# toolchain: msvc2015
|
||||
# libtype: static
|
||||
# libfiles: libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
## }
|
||||
#
|
||||
## windows vs2017 {
|
||||
#
|
||||
#- job: windows_386_msvc2017_shared
|
||||
# displayName: 'Windows 386 MSVC2017 Shared'
|
||||
# pool:
|
||||
# vmImage: 'vs2017-win2016'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
# defaultLibrary: shared
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: 386
|
||||
# toolchain: msvc2017
|
||||
# libtype: shared
|
||||
# libfiles: libui.dll libui.exp libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
#- job: windows_386_msvc2017_static
|
||||
# displayName: 'Windows 386 MSVC2017 Static'
|
||||
# pool:
|
||||
# vmImage: 'vs2017-win2016'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
# afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: 386
|
||||
# toolchain: msvc2017
|
||||
# libtype: static
|
||||
# libfiles: libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
#- job: windows_amd64_msvc2017_shared
|
||||
# displayName: 'Windows amd64 MSVC2017 Shared'
|
||||
# pool:
|
||||
# vmImage: 'vs2017-win2016'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
# defaultLibrary: shared
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: amd64
|
||||
# toolchain: msvc2017
|
||||
# libtype: shared
|
||||
# libfiles: libui.dll libui.exp libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
#- job: windows_amd64_msvc2017_static
|
||||
# displayName: 'Windows amd64 MSVC2017 Static'
|
||||
# pool:
|
||||
# vmImage: 'vs2017-win2016'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/windows-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# parameters:
|
||||
# beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
# afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
# - template: azure-pipelines/windows-artifacts.yml
|
||||
# parameters:
|
||||
# os: windows
|
||||
# arch: amd64
|
||||
# toolchain: msvc2017
|
||||
# libtype: static
|
||||
# libfiles: libui.lib
|
||||
# osHeader: ui_windows.h
|
||||
#
|
||||
## }
|
||||
#
|
||||
## mac {
|
||||
#
|
||||
## TODO beforeConfigure/beforeBuild: export SDKROOT=$(xcodebuild -version -sdk macosx10.13 Path)?
|
||||
#
|
||||
#- job: darwin_amd64_shared
|
||||
# displayName: 'Darwin amd64 Shared'
|
||||
# pool:
|
||||
# vmImage: 'macos-10.13'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/darwin-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# defaultLibrary: shared
|
||||
# - template: azure-pipelines/build.yml
|
||||
# - template: azure-pipelines/artifacts.yml
|
||||
# parameters:
|
||||
# os: darwin
|
||||
# arch: amd64
|
||||
# libtype: shared
|
||||
# libfiles: libui.A.dylib
|
||||
# osHeader: ui_darwin.h
|
||||
#
|
||||
#- job: darwin_amd64_static
|
||||
# displayName: 'Darwin amd64 Static'
|
||||
# pool:
|
||||
# vmImage: 'macos-10.13'
|
||||
# workspace:
|
||||
# clean: all
|
||||
# steps:
|
||||
# - template: azure-pipelines/setup-python3.yml
|
||||
# - template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
# - template: azure-pipelines/darwin-install-ninja.yml
|
||||
# - template: azure-pipelines/configure.yml
|
||||
# parameters:
|
||||
# defaultLibrary: static
|
||||
# - template: azure-pipelines/build.yml
|
||||
# - template: azure-pipelines/artifacts.yml
|
||||
# parameters:
|
||||
# os: darwin
|
||||
# arch: amd64
|
||||
# libtype: static
|
||||
# libfiles: libui.a
|
||||
# osHeader: ui_darwin.h
|
||||
#
|
||||
## }
|
||||
28
dep/libui/azure-pipelines/artifacts.yml
Normal file
28
dep/libui/azure-pipelines/artifacts.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
# 6 april 2019
|
||||
|
||||
parameters:
|
||||
os: ''
|
||||
arch: ''
|
||||
libtype: ''
|
||||
libfiles: ''
|
||||
osHeader: ''
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
set -x
|
||||
pushd build/meson-out
|
||||
cp ../../ui.h ../../${{ parameters.osHeader }} .
|
||||
tar czf $(Build.ArtifactStagingDirectory)/libui-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.libtype }}.tgz ${{ parameters.libfiles }} ui.h ${{ parameters.osHeader}}
|
||||
tar czf $(Build.ArtifactStagingDirectory)/examples-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.libtype }}.tgz $(releaseExamples)
|
||||
rm ui.h ${{ parameters.osHeader }}
|
||||
popd
|
||||
displayName: 'Create Artifacts'
|
||||
- task: GitHubRelease@0
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')
|
||||
inputs:
|
||||
gitHubConnection: andlabs
|
||||
repositoryName: andlabs/libui
|
||||
action: 'edit'
|
||||
addChangelog: false
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*'
|
||||
assetUploadMode: 'replace'
|
||||
12
dep/libui/azure-pipelines/build.yml
Normal file
12
dep/libui/azure-pipelines/build.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# 5 april 2019
|
||||
|
||||
parameters:
|
||||
beforeBuild: ''
|
||||
afterBuild: ''
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
${{ parameters.beforeBuild }}
|
||||
ninja -C build -v
|
||||
${{ parameters.afterBuild }}
|
||||
displayName: 'Build'
|
||||
43
dep/libui/azure-pipelines/collapse.awk
Normal file
43
dep/libui/azure-pipelines/collapse.awk
Normal file
@@ -0,0 +1,43 @@
|
||||
# 7 april 2019
|
||||
|
||||
BEGIN {
|
||||
RS = ""
|
||||
FS = "\n +- "
|
||||
}
|
||||
|
||||
/^- job:/ {
|
||||
for (i = 1; i <= NF; i++) {
|
||||
if (!(i in nextindex)) {
|
||||
# fast path for first occurrence
|
||||
lines[i, 0] = $i
|
||||
nextindex[i] = 1
|
||||
if (maxIndex < i)
|
||||
maxIndex = i
|
||||
continue
|
||||
}
|
||||
found = 0
|
||||
for (j = 0; j < nextindex[i]; j++)
|
||||
if (lines[i, j] == $i) {
|
||||
found = 1
|
||||
break
|
||||
}
|
||||
if (!found) {
|
||||
lines[i, nextindex[i]] = $i
|
||||
nextindex[i]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
END {
|
||||
for (i = 1; i <= maxIndex; i++) {
|
||||
if (nextindex[i] == 1) {
|
||||
# only one entry here, just print it
|
||||
print "- " lines[i, 0]
|
||||
continue
|
||||
}
|
||||
print "{"
|
||||
for (j = 0; j < nextindex[i]; j++)
|
||||
print "- " lines[i, j]
|
||||
print "}"
|
||||
}
|
||||
}
|
||||
298
dep/libui/azure-pipelines/collapsed
Normal file
298
dep/libui/azure-pipelines/collapsed
Normal file
@@ -0,0 +1,298 @@
|
||||
{
|
||||
- - job: linux_386_shared
|
||||
displayName: 'Linux 386 Shared'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: linux_386_static
|
||||
displayName: 'Linux 386 Static'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: linux_amd64_shared
|
||||
displayName: 'Linux amd64 Shared'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: linux_amd64_static
|
||||
displayName: 'Linux amd64 Static'
|
||||
pool:
|
||||
vmImage: 'ubuntu-16.04'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_386_msvc2015_shared
|
||||
displayName: 'Windows 386 MSVC2015 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_386_msvc2015_static
|
||||
displayName: 'Windows 386 MSVC2015 Static'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_amd64_msvc2015_shared
|
||||
displayName: 'Windows amd64 MSVC2015 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_amd64_msvc2015_static
|
||||
displayName: 'Windows amd64 MSVC2015 Static'
|
||||
pool:
|
||||
vmImage: 'vs2015-win2012r2'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_386_msvc2017_shared
|
||||
displayName: 'Windows 386 MSVC2017 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_386_msvc2017_static
|
||||
displayName: 'Windows 386 MSVC2017 Static'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_amd64_msvc2017_shared
|
||||
displayName: 'Windows amd64 MSVC2017 Shared'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: windows_amd64_msvc2017_static
|
||||
displayName: 'Windows amd64 MSVC2017 Static'
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: darwin_amd64_shared
|
||||
displayName: 'Darwin amd64 Shared'
|
||||
pool:
|
||||
vmImage: 'macos-10.13'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- - job: darwin_amd64_static
|
||||
displayName: 'Darwin amd64 Static'
|
||||
pool:
|
||||
vmImage: 'macos-10.13'
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
}
|
||||
{
|
||||
- template: azure-pipelines/setup-python3.yml
|
||||
- template: azure-pipelines/vs2015-install-python3.yml
|
||||
}
|
||||
- template: azure-pipelines/install-latest-meson-ninja.yml
|
||||
{
|
||||
- template: azure-pipelines/linux-386-install-gtk-dev.yml
|
||||
- template: azure-pipelines/linux-install-gtk-dev.yml
|
||||
- template: azure-pipelines/windows-install-ninja.yml
|
||||
- template: azure-pipelines/darwin-install-ninja.yml
|
||||
}
|
||||
{
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: export CFLAGS=-m32 CXXFLAGS=-m32 PKG_CONFIG_PATH=/usr/lib/i386-linux-gnu/pkgconfig
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
defaultLibrary: static
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
defaultLibrary: shared
|
||||
- template: azure-pipelines/configure.yml
|
||||
parameters:
|
||||
beforeConfigure: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
defaultLibrary: static
|
||||
}
|
||||
{
|
||||
- template: azure-pipelines/build.yml
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
- template: azure-pipelines/build.yml
|
||||
parameters:
|
||||
beforeBuild: call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
afterBuild: ren build\meson-out\libui.a libui.lib
|
||||
}
|
||||
{
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: 386
|
||||
libtype: shared
|
||||
libfiles: libui.so.0
|
||||
osHeader: ui_unix.h
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: 386
|
||||
libtype: static
|
||||
libfiles: libui.a
|
||||
osHeader: ui_unix.h
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: amd64
|
||||
libtype: shared
|
||||
libfiles: libui.so.0
|
||||
osHeader: ui_unix.h
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: linux
|
||||
arch: amd64
|
||||
libtype: static
|
||||
libfiles: libui.a
|
||||
osHeader: ui_unix.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2015
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2015
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2015
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2015
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2017
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: 386
|
||||
toolchain: msvc2017
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2017
|
||||
libtype: shared
|
||||
libfiles: libui.dll libui.exp libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/windows-artifacts.yml
|
||||
parameters:
|
||||
os: windows
|
||||
arch: amd64
|
||||
toolchain: msvc2017
|
||||
libtype: static
|
||||
libfiles: libui.lib
|
||||
osHeader: ui_windows.h
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
libtype: shared
|
||||
libfiles: libui.A.dylib
|
||||
osHeader: ui_darwin.h
|
||||
- template: azure-pipelines/artifacts.yml
|
||||
parameters:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
libtype: static
|
||||
libfiles: libui.a
|
||||
osHeader: ui_darwin.h
|
||||
}
|
||||
11
dep/libui/azure-pipelines/configure.yml
Normal file
11
dep/libui/azure-pipelines/configure.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
# 5 april 2019
|
||||
|
||||
parameters:
|
||||
beforeConfigure: ''
|
||||
defaultLibrary: 'must-be-set'
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
${{ parameters.beforeConfigure }}
|
||||
meson setup build --buildtype=release --default-library=${{ parameters.defaultLibrary }}
|
||||
displayName: 'Configure'
|
||||
13
dep/libui/azure-pipelines/darwin-install-ninja.yml
Normal file
13
dep/libui/azure-pipelines/darwin-install-ninja.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
# 5 april 2019
|
||||
# because brew install is also slow (it runs an update task first)
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
sudo mkdir -p /opt/ninja
|
||||
pushd /opt/ninja
|
||||
sudo wget https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-mac.zip
|
||||
sudo unzip ninja-mac.zip
|
||||
sudo chmod a+rx ninja
|
||||
popd
|
||||
echo '##vso[task.prependpath]/opt/ninja'
|
||||
displayName: 'Install Ninja'
|
||||
9
dep/libui/azure-pipelines/install-latest-meson-ninja.yml
Normal file
9
dep/libui/azure-pipelines/install-latest-meson-ninja.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
# 4 april 2019
|
||||
|
||||
# TODO remove ninja installation from non-Linux OSs and make the same ninja via pip change in the AppVeyor script
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install meson ninja
|
||||
displayName: 'Install Latest Meson and Ninja'
|
||||
17
dep/libui/azure-pipelines/linux-386-install-gtk-dev.yml
Normal file
17
dep/libui/azure-pipelines/linux-386-install-gtk-dev.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
# 7 april 2019
|
||||
|
||||
# TODO figure out how to get meson to recognize the compiler is producing 32-bit output
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
# Azure Pipelines ships with a patched version of this and that patch is only available on 64-bit systems, so trying to install the 32-bit versions will remove the 64-bit versions outright
|
||||
# This is a dependency of Mesa, so we'll have to downgrade to the stock distro ones :/
|
||||
llvmPackages=
|
||||
for i in libllvm6.0 clang-6.0 libclang-common-6.0-dev liblldb-6.0 liblldb-6.0-dev lld-6.0 lldb-6.0 llvm-6.0-dev python-lldb-6.0 libclang1-6.0 llvm-6.0 llvm-6.0-runtime; do llvmPackages="$llvmPackages $i=1:6.0-1ubuntu2~16.04.1"; done
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install --allow-downgrades \
|
||||
gcc-multilib g++-multilib \
|
||||
$llvmPackages \
|
||||
libgtk-3-dev:i386
|
||||
displayName: 'Install GTK+ Dev Files'
|
||||
6
dep/libui/azure-pipelines/linux-install-gtk-dev.yml
Normal file
6
dep/libui/azure-pipelines/linux-install-gtk-dev.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
# 7 april 2019
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
sudo apt-get install libgtk-3-dev
|
||||
displayName: 'Install GTK+ Dev Files'
|
||||
7
dep/libui/azure-pipelines/setup-python3.yml
Normal file
7
dep/libui/azure-pipelines/setup-python3.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
# 4 april 2019
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.6'
|
||||
architecture: 'x64'
|
||||
10
dep/libui/azure-pipelines/vs2015-install-python3.yml
Normal file
10
dep/libui/azure-pipelines/vs2015-install-python3.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# 4 april 2019
|
||||
# see https://github.com/Microsoft/azure-pipelines-image-generation/issues/374 for context and source
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
powershell -Command "Invoke-WebRequest https://www.python.org/ftp/python/3.7.1/python-3.7.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe"
|
||||
C:\py3-setup.exe /quiet PrependPath=0 InstallAllUsers=0 Include_launcher=0 InstallLauncherAllUsers=0 Include_test=0 Include_doc=0 Include_dev=0 Include_debug=0 Include_tcltk=0 TargetDir=C:\Python37
|
||||
@echo ##vso[task.prependpath]C:\Python37
|
||||
@echo ##vso[task.prependpath]C:\Python37\Scripts
|
||||
displayName: 'Install Python 3'
|
||||
28
dep/libui/azure-pipelines/windows-artifacts.yml
Normal file
28
dep/libui/azure-pipelines/windows-artifacts.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
# 6 april 2019
|
||||
|
||||
parameters:
|
||||
os: ''
|
||||
arch: ''
|
||||
toolchain: ''
|
||||
libtype: ''
|
||||
libfiles: ''
|
||||
osHeader: ''
|
||||
|
||||
steps:
|
||||
- powershell: |
|
||||
pushd build\meson-out
|
||||
Copy-Item @("..\..\ui.h","..\..\${{ parameters.osHeader }}") -Destination .
|
||||
Compress-Archive -Destination $(Build.ArtifactStagingDirectory)\libui-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.toolchain }}-${{ parameters.libtype }}.zip -Path @("${{ parameters.libfiles }}".Split(" ") + @("ui.h","${{ parameters.osHeader}}"))
|
||||
Compress-Archive -Destination $(Build.ArtifactStagingDirectory)\examples-$(Build.SourceBranchName)-${{ parameters.os }}-${{ parameters.arch }}-${{ parameters.libtype }}.zip -Path @("$(releaseExamples)".Split(" ") | % {$_ + ".exe"})
|
||||
Remove-Item @("ui.h","${{ parameters.osHeader }}")
|
||||
popd
|
||||
displayName: 'Create Artifacts'
|
||||
- task: GitHubRelease@0
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/')
|
||||
inputs:
|
||||
gitHubConnection: andlabs
|
||||
repositoryName: andlabs/libui
|
||||
action: 'edit'
|
||||
addChangelog: false
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*'
|
||||
assetUploadMode: 'replace'
|
||||
10
dep/libui/azure-pipelines/windows-install-ninja.yml
Normal file
10
dep/libui/azure-pipelines/windows-install-ninja.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# 4 april 2019
|
||||
# why this? because choco isn't available on the VS2015 image and is extremely slow on the VS2017 one (it should not take 2.5 minutes to install just ninja!)
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
powershell -Command "Invoke-WebRequest https://github.com/ninja-build/ninja/releases/download/v1.9.0/ninja-win.zip -OutFile C:\ninja-win.zip"
|
||||
mkdir C:\ninja
|
||||
powershell -Command "Expand-Archive -LiteralPath C:\ninja-win.zip -DestinationPath C:\ninja"
|
||||
@echo ##vso[task.prependpath]C:\ninja
|
||||
displayName: 'Install Ninja'
|
||||
169
dep/libui/common/areaevents.c
Normal file
169
dep/libui/common/areaevents.c
Normal file
@@ -0,0 +1,169 @@
|
||||
// 29 march 2014
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
/*
|
||||
Windows and GTK+ have a limit of 2 and 3 clicks, respectively, natively supported. Fortunately, we can simulate the double/triple-click behavior to build higher-order clicks. We can use the same algorithm Windows uses on both:
|
||||
http://blogs.msdn.com/b/oldnewthing/archive/2004/10/18/243925.aspx
|
||||
For GTK+, we pull the double-click time and double-click distance, which work the same as the equivalents on Windows (so the distance is in all directions), from the GtkSettings system.
|
||||
|
||||
On GTK+ this will also allow us to discard the GDK_BUTTON_2PRESS and GDK_BUTTON_3PRESS events, so the button press stream will be just like on other platforms.
|
||||
|
||||
Thanks to mclasen, garnacho_, halfline, and tristan in irc.gimp.net/#gtk+.
|
||||
|
||||
TODO note the bits about asymmetry and g_rcClick initial value not mattering in the oldnewthing article
|
||||
*/
|
||||
|
||||
// x, y, xdist, ydist, and c.rect must have the same units
|
||||
// so must time, maxTime, and c.prevTime
|
||||
int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist)
|
||||
{
|
||||
// different button than before? if so, don't count
|
||||
if (button != c->curButton)
|
||||
c->count = 0;
|
||||
|
||||
// (x, y) in the allowed region for a double-click? if not, don't count
|
||||
if (x < c->rectX0)
|
||||
c->count = 0;
|
||||
if (y < c->rectY0)
|
||||
c->count = 0;
|
||||
if (x >= c->rectX1)
|
||||
c->count = 0;
|
||||
if (y >= c->rectY1)
|
||||
c->count = 0;
|
||||
|
||||
// too slow? if so, don't count
|
||||
// note the below expression; time > (c.prevTime + maxTime) can overflow!
|
||||
if ((time - c->prevTime) > maxTime) // too slow; don't count
|
||||
c->count = 0;
|
||||
|
||||
c->count++; // if either of the above ifs happened, this will make the click count 1; otherwise it will make the click count 2, 3, 4, 5, ...
|
||||
|
||||
// now we need to update the internal structures for the next test
|
||||
c->curButton = button;
|
||||
c->prevTime = time;
|
||||
c->rectX0 = x - xdist;
|
||||
c->rectY0 = y - ydist;
|
||||
c->rectX1 = x + xdist;
|
||||
c->rectY1 = y + ydist;
|
||||
|
||||
return c->count;
|
||||
}
|
||||
|
||||
void uiprivClickCounterReset(uiprivClickCounter *c)
|
||||
{
|
||||
c->curButton = 0;
|
||||
c->rectX0 = 0;
|
||||
c->rectY0 = 0;
|
||||
c->rectX1 = 0;
|
||||
c->rectY1 = 0;
|
||||
c->prevTime = 0;
|
||||
c->count = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
For position independence across international keyboard layouts, typewriter keys are read using scancodes (which are always set 1).
|
||||
Windows provides the scancodes directly in the LPARAM.
|
||||
GTK+ provides the scancodes directly from the underlying window system via GdkEventKey.hardware_keycode.
|
||||
On X11, this is scancode + 8 (because X11 keyboard codes have a range of [8,255]).
|
||||
Wayland is guaranteed to give the same result (thanks ebassi in irc.gimp.net/#gtk+).
|
||||
On Linux, where evdev is used instead of polling scancodes directly from the keyboard, evdev's typewriter section key code constants are the same as scancodes anyway, so the rules above apply.
|
||||
Typewriter section scancodes are the same across international keyboards with some exceptions that have been accounted for (see KeyEvent's documentation); see http://www.quadibloc.com/comp/scan.htm for details.
|
||||
Non-typewriter keys can be handled safely using constants provided by the respective backend API.
|
||||
|
||||
Because GTK+ keysyms may or may not obey Num Lock, we also handle the 0-9 and . keys on the numeric keypad with scancodes (they match too).
|
||||
*/
|
||||
|
||||
// use uintptr_t to be safe; the size of the scancode/hardware key code field on each platform is different
|
||||
static const struct {
|
||||
uintptr_t scancode;
|
||||
char equiv;
|
||||
} scancodeKeys[] = {
|
||||
{ 0x02, '1' },
|
||||
{ 0x03, '2' },
|
||||
{ 0x04, '3' },
|
||||
{ 0x05, '4' },
|
||||
{ 0x06, '5' },
|
||||
{ 0x07, '6' },
|
||||
{ 0x08, '7' },
|
||||
{ 0x09, '8' },
|
||||
{ 0x0A, '9' },
|
||||
{ 0x0B, '0' },
|
||||
{ 0x0C, '-' },
|
||||
{ 0x0D, '=' },
|
||||
{ 0x0E, '\b' },
|
||||
{ 0x0F, '\t' },
|
||||
{ 0x10, 'q' },
|
||||
{ 0x11, 'w' },
|
||||
{ 0x12, 'e' },
|
||||
{ 0x13, 'r' },
|
||||
{ 0x14, 't' },
|
||||
{ 0x15, 'y' },
|
||||
{ 0x16, 'u' },
|
||||
{ 0x17, 'i' },
|
||||
{ 0x18, 'o' },
|
||||
{ 0x19, 'p' },
|
||||
{ 0x1A, '[' },
|
||||
{ 0x1B, ']' },
|
||||
{ 0x1C, '\n' },
|
||||
{ 0x1E, 'a' },
|
||||
{ 0x1F, 's' },
|
||||
{ 0x20, 'd' },
|
||||
{ 0x21, 'f' },
|
||||
{ 0x22, 'g' },
|
||||
{ 0x23, 'h' },
|
||||
{ 0x24, 'j' },
|
||||
{ 0x25, 'k' },
|
||||
{ 0x26, 'l' },
|
||||
{ 0x27, ';' },
|
||||
{ 0x28, '\'' },
|
||||
{ 0x29, '`' },
|
||||
{ 0x2B, '\\' },
|
||||
{ 0x2C, 'z' },
|
||||
{ 0x2D, 'x' },
|
||||
{ 0x2E, 'c' },
|
||||
{ 0x2F, 'v' },
|
||||
{ 0x30, 'b' },
|
||||
{ 0x31, 'n' },
|
||||
{ 0x32, 'm' },
|
||||
{ 0x33, ',' },
|
||||
{ 0x34, '.' },
|
||||
{ 0x35, '/' },
|
||||
{ 0x39, ' ' },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t scancode;
|
||||
uiExtKey equiv;
|
||||
} scancodeExtKeys[] = {
|
||||
{ 0x47, uiExtKeyN7 },
|
||||
{ 0x48, uiExtKeyN8 },
|
||||
{ 0x49, uiExtKeyN9 },
|
||||
{ 0x4B, uiExtKeyN4 },
|
||||
{ 0x4C, uiExtKeyN5 },
|
||||
{ 0x4D, uiExtKeyN6 },
|
||||
{ 0x4F, uiExtKeyN1 },
|
||||
{ 0x50, uiExtKeyN2 },
|
||||
{ 0x51, uiExtKeyN3 },
|
||||
{ 0x52, uiExtKeyN0 },
|
||||
{ 0x53, uiExtKeyNDot },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
int uiprivFromScancode(uintptr_t scancode, uiAreaKeyEvent *ke)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; scancodeKeys[i].scancode != 0xFFFF; i++)
|
||||
if (scancodeKeys[i].scancode == scancode) {
|
||||
ke->Key = scancodeKeys[i].equiv;
|
||||
return 1;
|
||||
}
|
||||
for (i = 0; scancodeExtKeys[i].scancode != 0xFFFF; i++)
|
||||
if (scancodeExtKeys[i].scancode == scancode) {
|
||||
ke->ExtKey = scancodeExtKeys[i].equiv;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
266
dep/libui/common/attribute.c
Normal file
266
dep/libui/common/attribute.c
Normal file
@@ -0,0 +1,266 @@
|
||||
// 19 february 2018
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
#include "attrstr.h"
|
||||
|
||||
struct uiAttribute {
|
||||
int ownedByUser;
|
||||
size_t refcount;
|
||||
uiAttributeType type;
|
||||
union {
|
||||
char *family;
|
||||
double size;
|
||||
uiTextWeight weight;
|
||||
uiTextItalic italic;
|
||||
uiTextStretch stretch;
|
||||
struct {
|
||||
double r;
|
||||
double g;
|
||||
double b;
|
||||
double a;
|
||||
// put this here so we can reuse this structure
|
||||
uiUnderlineColor underlineColor;
|
||||
} color;
|
||||
uiUnderline underline;
|
||||
uiOpenTypeFeatures *features;
|
||||
} u;
|
||||
};
|
||||
|
||||
static uiAttribute *newAttribute(uiAttributeType type)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = uiprivNew(uiAttribute);
|
||||
a->ownedByUser = 1;
|
||||
a->refcount = 0;
|
||||
a->type = type;
|
||||
return a;
|
||||
}
|
||||
|
||||
// returns a to allow expressions like b = uiprivAttributeRetain(a)
|
||||
// TODO would this allow us to copy attributes between strings in a foreach func, and if so, should that be allowed?
|
||||
uiAttribute *uiprivAttributeRetain(uiAttribute *a)
|
||||
{
|
||||
a->ownedByUser = 0;
|
||||
a->refcount++;
|
||||
return a;
|
||||
}
|
||||
|
||||
static void destroy(uiAttribute *a)
|
||||
{
|
||||
switch (a->type) {
|
||||
case uiAttributeTypeFamily:
|
||||
uiprivFree(a->u.family);
|
||||
break;
|
||||
case uiAttributeTypeFeatures:
|
||||
uiFreeOpenTypeFeatures(a->u.features);
|
||||
break;
|
||||
}
|
||||
uiprivFree(a);
|
||||
}
|
||||
|
||||
void uiprivAttributeRelease(uiAttribute *a)
|
||||
{
|
||||
if (a->ownedByUser)
|
||||
/* TODO implementation bug: we can't release an attribute we don't own */;
|
||||
a->refcount--;
|
||||
if (a->refcount == 0)
|
||||
destroy(a);
|
||||
}
|
||||
|
||||
void uiFreeAttribute(uiAttribute *a)
|
||||
{
|
||||
if (!a->ownedByUser)
|
||||
/* TODO user bug: you can't free an attribute you don't own */;
|
||||
destroy(a);
|
||||
}
|
||||
|
||||
uiAttributeType uiAttributeGetType(const uiAttribute *a)
|
||||
{
|
||||
return a->type;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewFamilyAttribute(const char *family)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeFamily);
|
||||
a->u.family = (char *) uiprivAlloc((strlen(family) + 1) * sizeof (char), "char[] (uiAttribute)");
|
||||
strcpy(a->u.family, family);
|
||||
return a;
|
||||
}
|
||||
|
||||
const char *uiAttributeFamily(const uiAttribute *a)
|
||||
{
|
||||
return a->u.family;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewSizeAttribute(double size)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeSize);
|
||||
a->u.size = size;
|
||||
return a;
|
||||
}
|
||||
|
||||
double uiAttributeSize(const uiAttribute *a)
|
||||
{
|
||||
return a->u.size;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewWeightAttribute(uiTextWeight weight)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeWeight);
|
||||
a->u.weight = weight;
|
||||
return a;
|
||||
}
|
||||
|
||||
uiTextWeight uiAttributeWeight(const uiAttribute *a)
|
||||
{
|
||||
return a->u.weight;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewItalicAttribute(uiTextItalic italic)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeItalic);
|
||||
a->u.italic = italic;
|
||||
return a;
|
||||
}
|
||||
|
||||
uiTextItalic uiAttributeItalic(const uiAttribute *a)
|
||||
{
|
||||
return a->u.italic;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewStretchAttribute(uiTextStretch stretch)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeStretch);
|
||||
a->u.stretch = stretch;
|
||||
return a;
|
||||
}
|
||||
|
||||
uiTextStretch uiAttributeStretch(const uiAttribute *a)
|
||||
{
|
||||
return a->u.stretch;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewColorAttribute(double r, double g, double b, double a)
|
||||
{
|
||||
uiAttribute *at;
|
||||
|
||||
at = newAttribute(uiAttributeTypeColor);
|
||||
at->u.color.r = r;
|
||||
at->u.color.g = g;
|
||||
at->u.color.b = b;
|
||||
at->u.color.a = a;
|
||||
return at;
|
||||
}
|
||||
|
||||
void uiAttributeColor(const uiAttribute *a, double *r, double *g, double *b, double *alpha)
|
||||
{
|
||||
*r = a->u.color.r;
|
||||
*g = a->u.color.g;
|
||||
*b = a->u.color.b;
|
||||
*alpha = a->u.color.a;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewBackgroundAttribute(double r, double g, double b, double a)
|
||||
{
|
||||
uiAttribute *at;
|
||||
|
||||
at = newAttribute(uiAttributeTypeBackground);
|
||||
at->u.color.r = r;
|
||||
at->u.color.g = g;
|
||||
at->u.color.b = b;
|
||||
at->u.color.a = a;
|
||||
return at;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewUnderlineAttribute(uiUnderline u)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeUnderline);
|
||||
a->u.underline = u;
|
||||
return a;
|
||||
}
|
||||
|
||||
uiUnderline uiAttributeUnderline(const uiAttribute *a)
|
||||
{
|
||||
return a->u.underline;
|
||||
}
|
||||
|
||||
uiAttribute *uiNewUnderlineColorAttribute(uiUnderlineColor u, double r, double g, double b, double a)
|
||||
{
|
||||
uiAttribute *at;
|
||||
|
||||
at = uiNewColorAttribute(r, g, b, a);
|
||||
at->type = uiAttributeTypeUnderlineColor;
|
||||
at->u.color.underlineColor = u;
|
||||
return at;
|
||||
}
|
||||
|
||||
void uiAttributeUnderlineColor(const uiAttribute *a, uiUnderlineColor *u, double *r, double *g, double *b, double *alpha)
|
||||
{
|
||||
*u = a->u.color.underlineColor;
|
||||
uiAttributeColor(a, r, g, b, alpha);
|
||||
}
|
||||
|
||||
uiAttribute *uiNewFeaturesAttribute(const uiOpenTypeFeatures *otf)
|
||||
{
|
||||
uiAttribute *a;
|
||||
|
||||
a = newAttribute(uiAttributeTypeFeatures);
|
||||
a->u.features = uiOpenTypeFeaturesClone(otf);
|
||||
return a;
|
||||
}
|
||||
|
||||
const uiOpenTypeFeatures *uiAttributeFeatures(const uiAttribute *a)
|
||||
{
|
||||
return a->u.features;
|
||||
}
|
||||
|
||||
int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b)
|
||||
{
|
||||
if (a == b)
|
||||
return 1;
|
||||
if (a->type != b->type)
|
||||
return 0;
|
||||
switch (a->type) {
|
||||
case uiAttributeTypeFamily:
|
||||
return uiprivStricmp(a->u.family, b->u.family);
|
||||
case uiAttributeTypeSize:
|
||||
// TODO is the use of == correct?
|
||||
return a->u.size == b->u.size;
|
||||
case uiAttributeTypeWeight:
|
||||
return a->u.weight == b->u.weight;
|
||||
case uiAttributeTypeItalic:
|
||||
return a->u.italic == b->u.italic;
|
||||
case uiAttributeTypeStretch:
|
||||
return a->u.stretch == b->u.stretch;
|
||||
case uiAttributeTypeUnderline:
|
||||
return a->u.underline == b->u.underline;
|
||||
case uiAttributeTypeUnderlineColor:
|
||||
if (a->u.color.underlineColor != b->u.color.underlineColor)
|
||||
return 0;
|
||||
// fall through
|
||||
case uiAttributeTypeColor:
|
||||
case uiAttributeTypeBackground:
|
||||
// TODO is the use of == correct?
|
||||
return (a->u.color.r == b->u.color.r) &&
|
||||
(a->u.color.g == b->u.color.g) &&
|
||||
(a->u.color.b == b->u.color.b) &&
|
||||
(a->u.color.a == b->u.color.a);
|
||||
case uiAttributeTypeFeatures:
|
||||
return uiprivOpenTypeFeaturesEqual(a->u.features, b->u.features);
|
||||
}
|
||||
// TODO should not be reached
|
||||
return 0;
|
||||
}
|
||||
612
dep/libui/common/attrlist.c
Normal file
612
dep/libui/common/attrlist.c
Normal file
@@ -0,0 +1,612 @@
|
||||
// 16 december 2016
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
#include "attrstr.h"
|
||||
|
||||
/*
|
||||
An attribute list is a doubly linked list of attributes.
|
||||
Attribute start positions are inclusive and attribute end positions are exclusive (or in other words, [start, end)).
|
||||
The list is kept sorted in increasing order by start position. Whether or not the sort is stable is undefined, so no temporal information should be expected to stay.
|
||||
Overlapping attributes are not allowed; if an attribute is added that conflicts with an existing one, the existing one is removed.
|
||||
In addition, the linked list tries to reduce fragmentation: if an attribute is added that just expands another, then there will only be one entry in alist, not two. (TODO does it really?)
|
||||
The linked list is not a ring; alist->fist->prev == NULL and alist->last->next == NULL.
|
||||
TODO verify that this disallows attributes of length zero
|
||||
*/
|
||||
|
||||
struct attr {
|
||||
uiAttribute *val;
|
||||
size_t start;
|
||||
size_t end;
|
||||
struct attr *prev;
|
||||
struct attr *next;
|
||||
};
|
||||
|
||||
struct uiprivAttrList {
|
||||
struct attr *first;
|
||||
struct attr *last;
|
||||
};
|
||||
|
||||
// if before is NULL, add to the end of the list
|
||||
static void attrInsertBefore(uiprivAttrList *alist, struct attr *a, struct attr *before)
|
||||
{
|
||||
// if the list is empty, this is the first item
|
||||
if (alist->first == NULL) {
|
||||
alist->first = a;
|
||||
alist->last = a;
|
||||
return;
|
||||
}
|
||||
|
||||
// add to the end
|
||||
if (before == NULL) {
|
||||
struct attr *oldlast;
|
||||
|
||||
oldlast = alist->last;
|
||||
alist->last = a;
|
||||
a->prev = oldlast;
|
||||
oldlast->next = a;
|
||||
return;
|
||||
}
|
||||
|
||||
// add to the beginning
|
||||
if (before == alist->first) {
|
||||
struct attr *oldfirst;
|
||||
|
||||
oldfirst = alist->first;
|
||||
alist->first = a;
|
||||
oldfirst->prev = a;
|
||||
a->next = oldfirst;
|
||||
return;
|
||||
}
|
||||
|
||||
// add to the middle
|
||||
a->prev = before->prev;
|
||||
a->next = before;
|
||||
before->prev = a;
|
||||
a->prev->next = a;
|
||||
}
|
||||
|
||||
static int attrHasPos(struct attr *a, size_t pos)
|
||||
{
|
||||
if (pos < a->start)
|
||||
return 0;
|
||||
return pos < a->end;
|
||||
}
|
||||
|
||||
// returns 1 if there was an intersection and 0 otherwise
|
||||
static int attrRangeIntersect(struct attr *a, size_t *start, size_t *end)
|
||||
{
|
||||
// is the range outside a entirely?
|
||||
if (*start >= a->end)
|
||||
return 0;
|
||||
if (*end < a->start)
|
||||
return 0;
|
||||
|
||||
// okay, so there is an overlap
|
||||
// compute the intersection
|
||||
if (*start < a->start)
|
||||
*start = a->start;
|
||||
if (*end > a->end)
|
||||
*end = a->end;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// returns the old a->next, for forward iteration
|
||||
static struct attr *attrUnlink(uiprivAttrList *alist, struct attr *a)
|
||||
{
|
||||
struct attr *p, *n;
|
||||
|
||||
p = a->prev;
|
||||
n = a->next;
|
||||
a->prev = NULL;
|
||||
a->next = NULL;
|
||||
|
||||
// only item in list?
|
||||
if (p == NULL && n == NULL) {
|
||||
alist->first = NULL;
|
||||
alist->last = NULL;
|
||||
return NULL;
|
||||
}
|
||||
// start of list?
|
||||
if (p == NULL) {
|
||||
n->prev = NULL;
|
||||
alist->first = n;
|
||||
return n;
|
||||
}
|
||||
// end of list?
|
||||
if (n == NULL) {
|
||||
p->next = NULL;
|
||||
alist->last = p;
|
||||
return NULL;
|
||||
}
|
||||
// middle of list
|
||||
p->next = n;
|
||||
n->prev = p;
|
||||
return n;
|
||||
}
|
||||
|
||||
// returns the old a->next, for forward iteration
|
||||
static struct attr *attrDelete(uiprivAttrList *alist, struct attr *a)
|
||||
{
|
||||
struct attr *next;
|
||||
|
||||
next = attrUnlink(alist, a);
|
||||
uiprivAttributeRelease(a->val);
|
||||
uiprivFree(a);
|
||||
return next;
|
||||
}
|
||||
|
||||
// attrDropRange() removes attributes without deleting characters.
|
||||
//
|
||||
// If the attribute needs no change, then nothing is done.
|
||||
//
|
||||
// If the attribute needs to be deleted, it is deleted.
|
||||
//
|
||||
// If the attribute only needs to be resized at the end, it is adjusted.
|
||||
//
|
||||
// If the attribute only needs to be resized at the start, it is adjusted, unlinked, and returned in tail.
|
||||
//
|
||||
// Otherwise, the attribute needs to be split. The existing attribute is adjusted to make the left half and a new attribute with the right half. This attribute is kept unlinked and returned in tail.
|
||||
//
|
||||
// In all cases, the return value is the next attribute to look at in a forward sequential loop.
|
||||
static struct attr *attrDropRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end, struct attr **tail)
|
||||
{
|
||||
struct attr *b;
|
||||
|
||||
// always pre-initialize tail to NULL
|
||||
*tail = NULL;
|
||||
|
||||
if (!attrRangeIntersect(a, &start, &end))
|
||||
// out of range; nothing to do
|
||||
return a->next;
|
||||
|
||||
// just outright delete the attribute?
|
||||
// the inequalities handle attributes entirely inside the range
|
||||
// if both are equal, the attribute's range is equal to the range
|
||||
if (a->start >= start && a->end <= end)
|
||||
return attrDelete(alist, a);
|
||||
|
||||
// only chop off the start or end?
|
||||
if (a->start == start) { // chop off the start
|
||||
// we are dropping the left half, so set a->start and unlink
|
||||
a->start = end;
|
||||
*tail = a;
|
||||
return attrUnlink(alist, a);
|
||||
}
|
||||
if (a->end == end) { // chop off the end
|
||||
// we are dropping the right half, so just set a->end
|
||||
a->end = start;
|
||||
return a->next;
|
||||
}
|
||||
|
||||
// we'll need to split the attribute into two
|
||||
b = uiprivNew(struct attr);
|
||||
b->val = uiprivAttributeRetain(a->val);
|
||||
b->start = end;
|
||||
b->end = a->end;
|
||||
*tail = b;
|
||||
|
||||
a->end = start;
|
||||
return a->next;
|
||||
}
|
||||
|
||||
static void attrGrow(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)
|
||||
{
|
||||
struct attr *before;
|
||||
|
||||
// adjusting the end is simple: if it ends before our new end, just set the new end
|
||||
if (a->end < end)
|
||||
a->end = end;
|
||||
|
||||
// adjusting the start is harder
|
||||
// if the start is before our new start, we are done
|
||||
// otherwise, we have to move the start back AND reposition the attribute to keep the sorted order
|
||||
if (a->start <= start)
|
||||
return;
|
||||
a->start = start;
|
||||
attrUnlink(alist, a);
|
||||
for (before = alist->first; before != NULL; before = before->next)
|
||||
if (before->start > a->start)
|
||||
break;
|
||||
attrInsertBefore(alist, a, before);
|
||||
}
|
||||
|
||||
// returns the right side of the split, which is unlinked, or NULL if no split was done
|
||||
static struct attr *attrSplitAt(uiprivAttrList *alist, struct attr *a, size_t at)
|
||||
{
|
||||
struct attr *b;
|
||||
|
||||
// no splittng needed?
|
||||
// note the equality: we don't need to split at start or end
|
||||
// in the end case, the last split point is at - 1; at itself is outside the range, and at - 1 results in the right hand side having length 1
|
||||
if (at <= a->start)
|
||||
return NULL;
|
||||
if (at >= a->end)
|
||||
return NULL;
|
||||
|
||||
b = uiprivNew(struct attr);
|
||||
b->val = uiprivAttributeRetain(a->val);
|
||||
b->start = at;
|
||||
b->end = a->end;
|
||||
|
||||
a->end = at;
|
||||
return b;
|
||||
}
|
||||
|
||||
// attrDeleteRange() removes attributes while deleting characters.
|
||||
//
|
||||
// If the attribute does not include the deleted range, then nothing is done (though the start and end are adjusted as necessary).
|
||||
//
|
||||
// If the attribute needs to be deleted, it is deleted.
|
||||
//
|
||||
// Otherwise, the attribute only needs the start or end deleted, and it is adjusted.
|
||||
//
|
||||
// In all cases, the return value is the next attribute to look at in a forward sequential loop.
|
||||
// TODO rewrite this comment
|
||||
static struct attr *attrDeleteRange(uiprivAttrList *alist, struct attr *a, size_t start, size_t end)
|
||||
{
|
||||
size_t ostart, oend;
|
||||
size_t count;
|
||||
|
||||
ostart = start;
|
||||
oend = end;
|
||||
count = oend - ostart;
|
||||
|
||||
if (!attrRangeIntersect(a, &start, &end)) {
|
||||
// out of range
|
||||
// adjust if necessary
|
||||
if (a->start >= ostart)
|
||||
a->start -= count;
|
||||
if (a->end >= oend)
|
||||
a->end -= count;
|
||||
return a->next;
|
||||
}
|
||||
|
||||
// just outright delete the attribute?
|
||||
// the inequalities handle attributes entirely inside the range
|
||||
// if both are equal, the attribute's range is equal to the range
|
||||
if (a->start >= start && a->end <= end)
|
||||
return attrDelete(alist, a);
|
||||
|
||||
// only chop off the start or end?
|
||||
if (a->start == start) { // chop off the start
|
||||
// if we weren't adjusting positions this would just be setting a->start to end
|
||||
// but since this is deleting from the start, we need to adjust both by count
|
||||
a->start = end - count;
|
||||
a->end -= count;
|
||||
return a->next;
|
||||
}
|
||||
if (a->end == end) { // chop off the end
|
||||
// a->start is already good
|
||||
a->end = start;
|
||||
return a->next;
|
||||
}
|
||||
|
||||
// in this case, the deleted range is inside the attribute
|
||||
// we can clear it by just removing count from a->end
|
||||
a->end -= count;
|
||||
return a->next;
|
||||
}
|
||||
|
||||
uiprivAttrList *uiprivNewAttrList(void)
|
||||
{
|
||||
return uiprivNew(uiprivAttrList);
|
||||
}
|
||||
|
||||
void uiprivFreeAttrList(uiprivAttrList *alist)
|
||||
{
|
||||
struct attr *a, *next;
|
||||
|
||||
a = alist->first;
|
||||
while (a != NULL) {
|
||||
next = a->next;
|
||||
uiprivAttributeRelease(a->val);
|
||||
uiprivFree(a);
|
||||
a = next;
|
||||
}
|
||||
uiprivFree(alist);
|
||||
}
|
||||
|
||||
void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end)
|
||||
{
|
||||
struct attr *a;
|
||||
struct attr *before;
|
||||
struct attr *tail = NULL;
|
||||
int split = 0;
|
||||
uiAttributeType valtype;
|
||||
|
||||
// first, figure out where in the list this should go
|
||||
// in addition, if this attribute overrides one that already exists, split that one apart so this one can take over
|
||||
before = alist->first;
|
||||
valtype = uiAttributeGetType(val);
|
||||
while (before != NULL) {
|
||||
size_t lstart, lend;
|
||||
|
||||
// once we get to the first point after start, we know where to insert
|
||||
if (before->start > start)
|
||||
break;
|
||||
|
||||
// if we have already split a prior instance of this attribute, don't bother doing it again
|
||||
if (split)
|
||||
goto next;
|
||||
|
||||
// should we split this attribute?
|
||||
if (uiAttributeGetType(before->val) != valtype)
|
||||
goto next;
|
||||
lstart = start;
|
||||
lend = end;
|
||||
if (!attrRangeIntersect(before, &lstart, &lend))
|
||||
goto next;
|
||||
|
||||
// okay so this might conflict; if the val is the same as the one we want, we need to expand the existing attribute, not fragment anything
|
||||
// TODO will this reduce fragmentation if we first add from 0 to 2 and then from 2 to 4? or do we have to do that separately?
|
||||
if (uiprivAttributeEqual(before->val, val)) {
|
||||
attrGrow(alist, before, start, end);
|
||||
return;
|
||||
}
|
||||
// okay the values are different; we need to split apart
|
||||
before = attrDropRange(alist, before, start, end, &tail);
|
||||
split = 1;
|
||||
continue;
|
||||
|
||||
next:
|
||||
before = before->next;
|
||||
}
|
||||
|
||||
// if we got here, we know we have to add the attribute before before
|
||||
a = uiprivNew(struct attr);
|
||||
a->val = uiprivAttributeRetain(val);
|
||||
a->start = start;
|
||||
a->end = end;
|
||||
attrInsertBefore(alist, a, before);
|
||||
|
||||
// and finally, if we split, insert the remainder
|
||||
if (tail == NULL)
|
||||
return;
|
||||
// note we start at before; it won't be inserted before that by the sheer nature of how the code above works
|
||||
for (; before != NULL; before = before->next)
|
||||
if (before->start > tail->start)
|
||||
break;
|
||||
attrInsertBefore(alist, tail, before);
|
||||
}
|
||||
|
||||
void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count)
|
||||
{
|
||||
struct attr *a;
|
||||
struct attr *tails = NULL;
|
||||
|
||||
// every attribute before the insertion point can either cross into the insertion point or not
|
||||
// if it does, we need to split that attribute apart at the insertion point, keeping only the old attribute in place, adjusting the new tail, and preparing it for being re-added later
|
||||
for (a = alist->first; a != NULL; a = a->next) {
|
||||
struct attr *tail;
|
||||
|
||||
// stop once we get to the insertion point
|
||||
if (a->start >= start)
|
||||
break;
|
||||
// only do something if overlapping
|
||||
if (!attrHasPos(a, start))
|
||||
continue;
|
||||
|
||||
tail = attrSplitAt(alist, a, start);
|
||||
// adjust the new tail for the insertion
|
||||
tail->start += count;
|
||||
tail->end += count;
|
||||
// and queue it for re-adding later
|
||||
// we can safely use tails as if it was singly-linked since it's just a temporary list; we properly merge them back in below and they'll be doubly-linked again then
|
||||
// TODO actually we could probably save some time by making then doubly-linked now and adding them in one fell swoop, but that would make things a bit more complicated...
|
||||
tail->next = tails;
|
||||
tails = tail;
|
||||
}
|
||||
|
||||
// at this point in the attribute list, we are at or after the insertion point
|
||||
// all the split-apart attributes will be at the insertion point
|
||||
// therefore, we can just add them all back now, and the list will still be sorted correctly
|
||||
while (tails != NULL) {
|
||||
struct attr *next;
|
||||
|
||||
// make all the links NULL before insertion, just to be safe
|
||||
next = tails->next;
|
||||
tails->next = NULL;
|
||||
attrInsertBefore(alist, tails, a);
|
||||
tails = next;
|
||||
}
|
||||
|
||||
// every remaining attribute will be either at or after the insertion point
|
||||
// we just need to move them ahead
|
||||
for (; a != NULL; a = a->next) {
|
||||
a->start += count;
|
||||
a->end += count;
|
||||
}
|
||||
}
|
||||
|
||||
// The attributes are those of character start - 1.
|
||||
// If start == 0, the attributes are those of character 0.
|
||||
/*
|
||||
This is an obtuse function. Here's some diagrams to help.
|
||||
|
||||
Given the input string
|
||||
abcdefghi (positions: 012345678 9)
|
||||
and attribute set
|
||||
red start 0 end 3
|
||||
bold start 2 end 6
|
||||
underline start 5 end 8
|
||||
or visually:
|
||||
012345678 9
|
||||
rrr------
|
||||
--bbbb---
|
||||
-----uuu-
|
||||
If we insert qwe to result in positions 0123456789AB C:
|
||||
|
||||
before 0, 1, 2 (grow the red part, move everything else down)
|
||||
red -> start 0 (no change) end 3+3=6
|
||||
bold -> start 2+3=5 end 6+3=9
|
||||
underline -> start 5+3=8 end 8+3=B
|
||||
before 3 (grow red and bold, move underline down)
|
||||
red -> start 0 (no change) end 3+3=6
|
||||
bold -> start 2 (no change) end 6+3=9
|
||||
underline -> start 5+3=8 end 8+3=B
|
||||
before 4, 5 (keep red, grow bold, move underline down)
|
||||
red -> start 0 (no change) end 3 (no change)
|
||||
bold -> start 2 (no change) end 6+3=9
|
||||
underline -> start 5+3=8 end 8+3=B
|
||||
before 6 (keep red, grow bold and underline)
|
||||
red -> start 0 (no change) end 3 (no change)
|
||||
bold -> start 2 (no change) end 6+3=9
|
||||
underline -> start 5 (no change) end 8+3=B
|
||||
before 7, 8 (keep red and bold, grow underline)
|
||||
red -> start 0 (no change) end 3 (no change)
|
||||
bold -> start 2 (no change) end 6 (no change)
|
||||
underline -> start 5 (no change) end 8+3=B
|
||||
before 9 (keep all three)
|
||||
red -> start 0 (no change) end 3 (no change)
|
||||
bold -> start 2 (no change) end 6 (no change)
|
||||
underline -> start 5 (no change) end 8 (no change)
|
||||
|
||||
result:
|
||||
0 1 2 3 4 5 6 7 8 9
|
||||
red E E E e n n n n n n
|
||||
bold s s S E E E e n n n
|
||||
underline s s s s s S E E e n
|
||||
N = none
|
||||
E = end only
|
||||
S = start and end
|
||||
uppercase = in original range, lowercase = not
|
||||
|
||||
which results in our algorithm:
|
||||
for each attribute
|
||||
if start < insertion point
|
||||
move start up
|
||||
else if start == insertion point
|
||||
if start != 0
|
||||
move start up
|
||||
if end <= insertion point
|
||||
move end up
|
||||
*/
|
||||
// TODO does this ensure the list remains sorted?
|
||||
void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count)
|
||||
{
|
||||
struct attr *a;
|
||||
|
||||
for (a = alist->first; a != NULL; a = a->next) {
|
||||
if (a->start < start)
|
||||
a->start += count;
|
||||
else if (a->start == start && start != 0)
|
||||
a->start += count;
|
||||
if (a->end <= start)
|
||||
a->end += count;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO replace at point with — replaces with first character's attributes
|
||||
|
||||
void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end)
|
||||
{
|
||||
struct attr *a;
|
||||
struct attr *tails = NULL; // see uiprivAttrListInsertCharactersUnattributed() above
|
||||
struct attr *tailsAt = NULL;
|
||||
|
||||
a = alist->first;
|
||||
while (a != NULL) {
|
||||
size_t lstart, lend;
|
||||
struct attr *tail;
|
||||
|
||||
// this defines where to re-attach the tails
|
||||
// (all the tails will have their start at end, so we can just insert them all before tailsAt)
|
||||
if (a->start >= end) {
|
||||
tailsAt = a;
|
||||
// and at this point we're done, so
|
||||
break;
|
||||
}
|
||||
if (uiAttributeGetType(a->val) != type)
|
||||
goto next;
|
||||
lstart = start;
|
||||
lend = end;
|
||||
if (!attrRangeIntersect(a, &lstart, &lend))
|
||||
goto next;
|
||||
a = attrDropRange(alist, a, start, end, &tail);
|
||||
if (tail != NULL) {
|
||||
tail->next = tails;
|
||||
tails = tail;
|
||||
}
|
||||
continue;
|
||||
|
||||
next:
|
||||
a = a->next;
|
||||
}
|
||||
|
||||
while (tails != NULL) {
|
||||
struct attr *next;
|
||||
|
||||
// make all the links NULL before insertion, just to be safe
|
||||
next = tails->next;
|
||||
tails->next = NULL;
|
||||
attrInsertBefore(alist, tails, a);
|
||||
tails = next;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO merge this with the above
|
||||
void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end)
|
||||
{
|
||||
struct attr *a;
|
||||
struct attr *tails = NULL; // see uiprivAttrListInsertCharactersUnattributed() above
|
||||
struct attr *tailsAt = NULL;
|
||||
|
||||
a = alist->first;
|
||||
while (a != NULL) {
|
||||
size_t lstart, lend;
|
||||
struct attr *tail;
|
||||
|
||||
// this defines where to re-attach the tails
|
||||
// (all the tails will have their start at end, so we can just insert them all before tailsAt)
|
||||
if (a->start >= end) {
|
||||
tailsAt = a;
|
||||
// and at this point we're done, so
|
||||
break;
|
||||
}
|
||||
lstart = start;
|
||||
lend = end;
|
||||
if (!attrRangeIntersect(a, &lstart, &lend))
|
||||
goto next;
|
||||
a = attrDropRange(alist, a, start, end, &tail);
|
||||
if (tail != NULL) {
|
||||
tail->next = tails;
|
||||
tails = tail;
|
||||
}
|
||||
continue;
|
||||
|
||||
next:
|
||||
a = a->next;
|
||||
}
|
||||
|
||||
while (tails != NULL) {
|
||||
struct attr *next;
|
||||
|
||||
// make all the links NULL before insertion, just to be safe
|
||||
next = tails->next;
|
||||
tails->next = NULL;
|
||||
attrInsertBefore(alist, tails, a);
|
||||
tails = next;
|
||||
}
|
||||
}
|
||||
|
||||
void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end)
|
||||
{
|
||||
struct attr *a;
|
||||
|
||||
a = alist->first;
|
||||
while (a != NULL)
|
||||
a = attrDeleteRange(alist, a, start, end);
|
||||
}
|
||||
|
||||
void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)
|
||||
{
|
||||
struct attr *a;
|
||||
uiForEach ret;
|
||||
|
||||
for (a = alist->first; a != NULL; a = a->next) {
|
||||
ret = (*f)(s, a->val, a->start, a->end, data);
|
||||
if (ret == uiForEachStop)
|
||||
// TODO for all: break or return?
|
||||
break;
|
||||
}
|
||||
}
|
||||
357
dep/libui/common/attrstr.c
Normal file
357
dep/libui/common/attrstr.c
Normal file
@@ -0,0 +1,357 @@
|
||||
// 3 december 2016
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
#include "attrstr.h"
|
||||
|
||||
struct uiAttributedString {
|
||||
char *s;
|
||||
size_t len;
|
||||
|
||||
uiprivAttrList *attrs;
|
||||
|
||||
// indiscriminately keep a UTF-16 copy of the string on all platforms so we can hand this off to the grapheme calculator
|
||||
// this ensures no one platform has a speed advantage (sorry GTK+)
|
||||
uint16_t *u16;
|
||||
size_t u16len;
|
||||
|
||||
size_t *u8tou16;
|
||||
size_t *u16tou8;
|
||||
|
||||
// this is lazily created to keep things from getting *too* slow
|
||||
uiprivGraphemes *graphemes;
|
||||
};
|
||||
|
||||
static void resize(uiAttributedString *s, size_t u8, size_t u16)
|
||||
{
|
||||
s->len = u8;
|
||||
s->s = (char *) uiprivRealloc(s->s, (s->len + 1) * sizeof (char), "char[] (uiAttributedString)");
|
||||
s->u8tou16 = (size_t *) uiprivRealloc(s->u8tou16, (s->len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)");
|
||||
s->u16len = u16;
|
||||
s->u16 = (uint16_t *) uiprivRealloc(s->u16, (s->u16len + 1) * sizeof (uint16_t), "uint16_t[] (uiAttributedString)");
|
||||
s->u16tou8 = (size_t *) uiprivRealloc(s->u16tou8, (s->u16len + 1) * sizeof (size_t), "size_t[] (uiAttributedString)");
|
||||
}
|
||||
|
||||
uiAttributedString *uiNewAttributedString(const char *initialString)
|
||||
{
|
||||
uiAttributedString *s;
|
||||
|
||||
s = uiprivNew(uiAttributedString);
|
||||
s->attrs = uiprivNewAttrList();
|
||||
uiAttributedStringAppendUnattributed(s, initialString);
|
||||
return s;
|
||||
}
|
||||
|
||||
// TODO make sure that all implementations of uiprivNewGraphemes() work fine with empty strings; in particular, the Windows one might not
|
||||
static void recomputeGraphemes(uiAttributedString *s)
|
||||
{
|
||||
if (s->graphemes != NULL)
|
||||
return;
|
||||
if (uiprivGraphemesTakesUTF16()) {
|
||||
s->graphemes = uiprivNewGraphemes(s->u16, s->u16len);
|
||||
return;
|
||||
}
|
||||
s->graphemes = uiprivNewGraphemes(s->s, s->len);
|
||||
}
|
||||
|
||||
static void invalidateGraphemes(uiAttributedString *s)
|
||||
{
|
||||
if (s->graphemes == NULL)
|
||||
return;
|
||||
uiprivFree(s->graphemes->pointsToGraphemes);
|
||||
uiprivFree(s->graphemes->graphemesToPoints);
|
||||
uiprivFree(s->graphemes);
|
||||
s->graphemes = NULL;
|
||||
}
|
||||
|
||||
void uiFreeAttributedString(uiAttributedString *s)
|
||||
{
|
||||
uiprivFreeAttrList(s->attrs);
|
||||
invalidateGraphemes(s);
|
||||
uiprivFree(s->u16tou8);
|
||||
uiprivFree(s->u8tou16);
|
||||
uiprivFree(s->u16);
|
||||
uiprivFree(s->s);
|
||||
uiprivFree(s);
|
||||
}
|
||||
|
||||
const char *uiAttributedStringString(const uiAttributedString *s)
|
||||
{
|
||||
return s->s;
|
||||
}
|
||||
|
||||
size_t uiAttributedStringLen(const uiAttributedString *s)
|
||||
{
|
||||
return s->len;
|
||||
}
|
||||
|
||||
static void u8u16len(const char *str, size_t *n8, size_t *n16)
|
||||
{
|
||||
uint32_t rune;
|
||||
char buf[4];
|
||||
uint16_t buf16[2];
|
||||
|
||||
*n8 = 0;
|
||||
*n16 = 0;
|
||||
while (*str) {
|
||||
str = uiprivUTF8DecodeRune(str, 0, &rune);
|
||||
// TODO document the use of the function vs a pointer subtract here
|
||||
// TODO also we need to consider namespace collision with utf.h...
|
||||
*n8 += uiprivUTF8EncodeRune(rune, buf);
|
||||
*n16 += uiprivUTF16EncodeRune(rune, buf16);
|
||||
}
|
||||
}
|
||||
|
||||
void uiAttributedStringAppendUnattributed(uiAttributedString *s, const char *str)
|
||||
{
|
||||
uiAttributedStringInsertAtUnattributed(s, str, s->len);
|
||||
}
|
||||
|
||||
// this works (and returns true, which is what we want) at s->len too because s->s[s->len] is always going to be 0 due to us allocating s->len + 1 bytes and because uiprivRealloc() always zero-fills allocated memory
|
||||
static int onCodepointBoundary(uiAttributedString *s, size_t at)
|
||||
{
|
||||
uint8_t c;
|
||||
|
||||
// for uiNewAttributedString()
|
||||
if (s->s == NULL && at == 0)
|
||||
return 1;
|
||||
c = (uint8_t) (s->s[at]);
|
||||
return c < 0x80 || c >= 0xC0;
|
||||
}
|
||||
|
||||
// TODO note that at must be on a codeoint boundary
|
||||
void uiAttributedStringInsertAtUnattributed(uiAttributedString *s, const char *str, size_t at)
|
||||
{
|
||||
uint32_t rune;
|
||||
char buf[4];
|
||||
uint16_t buf16[2];
|
||||
size_t n8, n16; // TODO make loop-local? to avoid using them in the wrong place again
|
||||
size_t old, old16;
|
||||
size_t oldn8, oldn16;
|
||||
size_t oldlen, old16len;
|
||||
size_t at16;
|
||||
size_t i;
|
||||
|
||||
if (!onCodepointBoundary(s, at)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
at16 = 0;
|
||||
if (s->u8tou16 != NULL)
|
||||
at16 = s->u8tou16[at];
|
||||
|
||||
// do this first to reclaim memory
|
||||
invalidateGraphemes(s);
|
||||
|
||||
// first figure out how much we need to grow by
|
||||
// this includes post-validated UTF-8
|
||||
u8u16len(str, &n8, &n16);
|
||||
|
||||
// and resize
|
||||
old = at;
|
||||
old16 = at16;
|
||||
oldlen = s->len;
|
||||
old16len = s->u16len;
|
||||
resize(s, s->len + n8, s->u16len + n16);
|
||||
|
||||
// move existing characters out of the way
|
||||
// note the use of memmove(): https://twitter.com/rob_pike/status/737797688217894912
|
||||
memmove(
|
||||
s->s + at + n8,
|
||||
s->s + at,
|
||||
(oldlen - at) * sizeof (char));
|
||||
memmove(
|
||||
s->u16 + at16 + n16,
|
||||
s->u16 + at16,
|
||||
(old16len - at16) * sizeof (uint16_t));
|
||||
// note the + 1 for these; we want to copy the terminating null too
|
||||
memmove(
|
||||
s->u8tou16 + at + n8,
|
||||
s->u8tou16 + at,
|
||||
(oldlen - at + 1) * sizeof (size_t));
|
||||
memmove(
|
||||
s->u16tou8 + at16 + n16,
|
||||
s->u16tou8 + at16,
|
||||
(old16len - at16 + 1) * sizeof (size_t));
|
||||
oldn8 = n8;
|
||||
oldn16 = n16;
|
||||
|
||||
// and copy
|
||||
while (*str) {
|
||||
size_t n;
|
||||
|
||||
str = uiprivUTF8DecodeRune(str, 0, &rune);
|
||||
n = uiprivUTF8EncodeRune(rune, buf);
|
||||
n16 = uiprivUTF16EncodeRune(rune, buf16);
|
||||
s->s[old] = buf[0];
|
||||
s->u8tou16[old] = old16;
|
||||
if (n > 1) {
|
||||
s->s[old + 1] = buf[1];
|
||||
s->u8tou16[old + 1] = old16;
|
||||
}
|
||||
if (n > 2) {
|
||||
s->s[old + 2] = buf[2];
|
||||
s->u8tou16[old + 2] = old16;
|
||||
}
|
||||
if (n > 3) {
|
||||
s->s[old + 3] = buf[3];
|
||||
s->u8tou16[old + 3] = old16;
|
||||
}
|
||||
s->u16[old16] = buf16[0];
|
||||
s->u16tou8[old16] = old;
|
||||
if (n16 > 1) {
|
||||
s->u16[old16 + 1] = buf16[1];
|
||||
s->u16tou8[old16 + 1] = old;
|
||||
}
|
||||
old += n;
|
||||
old16 += n16;
|
||||
}
|
||||
// and have an index for the end of the string
|
||||
// TODO is this done by the below?
|
||||
//TODO s->u8tou16[old] = old16;
|
||||
//TODO s->u16tou8[old16] = old;
|
||||
|
||||
// and adjust the prior values in the conversion tables
|
||||
// use <= so the terminating 0 gets updated too
|
||||
for (i = 0; i <= oldlen - at; i++)
|
||||
s->u8tou16[at + oldn8 + i] += s->u16len - old16len;
|
||||
for (i = 0; i <= old16len - at16; i++)
|
||||
s->u16tou8[at16 + oldn16 + i] += s->len - oldlen;
|
||||
|
||||
// and finally do the attributes
|
||||
uiprivAttrListInsertCharactersUnattributed(s->attrs, at, n8);
|
||||
}
|
||||
|
||||
// TODO document that end is the first index that will be maintained
|
||||
void uiAttributedStringDelete(uiAttributedString *s, size_t start, size_t end)
|
||||
{
|
||||
size_t start16, end16;
|
||||
size_t count, count16;
|
||||
size_t i;
|
||||
|
||||
if (!onCodepointBoundary(s, start)) {
|
||||
// TODO
|
||||
}
|
||||
if (!onCodepointBoundary(s, end)) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
count = end - start;
|
||||
start16 = s->u8tou16[start];
|
||||
end16 = s->u8tou16[end];
|
||||
count16 = end16 - start16;
|
||||
|
||||
invalidateGraphemes(s);
|
||||
|
||||
// overwrite old characters
|
||||
memmove(
|
||||
s->s + start,
|
||||
s->s + end,
|
||||
(s->len - end) * sizeof (char));
|
||||
memmove(
|
||||
s->u16 + start16,
|
||||
s->u16 + end16,
|
||||
(s->u16len - end16) * sizeof (uint16_t));
|
||||
// note the + 1 for these; we want to copy the terminating null too
|
||||
memmove(
|
||||
s->u8tou16 + start,
|
||||
s->u8tou16 + end,
|
||||
(s->len - end + 1) * sizeof (size_t));
|
||||
memmove(
|
||||
s->u16tou8 + start16,
|
||||
s->u16tou8 + end16,
|
||||
(s->u16len - end16 + 1) * sizeof (size_t));
|
||||
|
||||
// update the conversion tables
|
||||
// note the use of <= to include the null terminator
|
||||
for (i = 0; i <= (s->len - end); i++)
|
||||
s->u8tou16[start + i] -= count16;
|
||||
for (i = 0; i <= (s->u16len - end16); i++)
|
||||
s->u16tou8[start16 + i] -= count;
|
||||
|
||||
// null-terminate the string
|
||||
s->s[start + (s->len - end)] = 0;
|
||||
s->u16[start16 + (s->u16len - end16)] = 0;
|
||||
|
||||
// fix up attributes
|
||||
uiprivAttrListRemoveCharacters(s->attrs, start, end);
|
||||
|
||||
// and finally resize
|
||||
resize(s, s->len - count, s->u16len - count16);
|
||||
}
|
||||
|
||||
void uiAttributedStringSetAttribute(uiAttributedString *s, uiAttribute *a, size_t start, size_t end)
|
||||
{
|
||||
uiprivAttrListInsertAttribute(s->attrs, a, start, end);
|
||||
}
|
||||
|
||||
// LONGTERM introduce an iterator object instead?
|
||||
void uiAttributedStringForEachAttribute(const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data)
|
||||
{
|
||||
uiprivAttrListForEach(s->attrs, s, f, data);
|
||||
}
|
||||
|
||||
// TODO figure out if we should count the grapheme past the end
|
||||
size_t uiAttributedStringNumGraphemes(uiAttributedString *s)
|
||||
{
|
||||
recomputeGraphemes(s);
|
||||
return s->graphemes->len;
|
||||
}
|
||||
|
||||
size_t uiAttributedStringByteIndexToGrapheme(uiAttributedString *s, size_t pos)
|
||||
{
|
||||
recomputeGraphemes(s);
|
||||
if (uiprivGraphemesTakesUTF16())
|
||||
pos = s->u8tou16[pos];
|
||||
return s->graphemes->pointsToGraphemes[pos];
|
||||
}
|
||||
|
||||
size_t uiAttributedStringGraphemeToByteIndex(uiAttributedString *s, size_t pos)
|
||||
{
|
||||
recomputeGraphemes(s);
|
||||
pos = s->graphemes->graphemesToPoints[pos];
|
||||
if (uiprivGraphemesTakesUTF16())
|
||||
pos = s->u16tou8[pos];
|
||||
return pos;
|
||||
}
|
||||
|
||||
// helpers for platform-specific code
|
||||
|
||||
const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s)
|
||||
{
|
||||
return s->u16;
|
||||
}
|
||||
|
||||
size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s)
|
||||
{
|
||||
return s->u16len;
|
||||
}
|
||||
|
||||
// TODO is this still needed given the below?
|
||||
size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n)
|
||||
{
|
||||
return s->u8tou16[n];
|
||||
}
|
||||
|
||||
size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n)
|
||||
{
|
||||
size_t *out;
|
||||
size_t nbytes;
|
||||
|
||||
nbytes = (s->len + 1) * sizeof (size_t);
|
||||
*n = s->len;
|
||||
out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)");
|
||||
memmove(out, s->u8tou16, nbytes);
|
||||
return out;
|
||||
}
|
||||
|
||||
size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n)
|
||||
{
|
||||
size_t *out;
|
||||
size_t nbytes;
|
||||
|
||||
nbytes = (s->u16len + 1) * sizeof (size_t);
|
||||
*n = s->u16len;
|
||||
out = (size_t *) uiprivAlloc(nbytes, "size_t[] (uiAttributedString)");
|
||||
memmove(out, s->u16tou8, nbytes);
|
||||
return out;
|
||||
}
|
||||
46
dep/libui/common/attrstr.h
Normal file
46
dep/libui/common/attrstr.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// 19 february 2018
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// attribute.c
|
||||
extern uiAttribute *uiprivAttributeRetain(uiAttribute *a);
|
||||
extern void uiprivAttributeRelease(uiAttribute *a);
|
||||
extern int uiprivAttributeEqual(const uiAttribute *a, const uiAttribute *b);
|
||||
|
||||
// opentype.c
|
||||
extern int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b);
|
||||
|
||||
// attrlist.c
|
||||
typedef struct uiprivAttrList uiprivAttrList;
|
||||
extern uiprivAttrList *uiprivNewAttrList(void);
|
||||
extern void uiprivFreeAttrList(uiprivAttrList *alist);
|
||||
extern void uiprivAttrListInsertAttribute(uiprivAttrList *alist, uiAttribute *val, size_t start, size_t end);
|
||||
extern void uiprivAttrListInsertCharactersUnattributed(uiprivAttrList *alist, size_t start, size_t count);
|
||||
extern void uiprivAttrListInsertCharactersExtendingAttributes(uiprivAttrList *alist, size_t start, size_t count);
|
||||
extern void uiprivAttrListRemoveAttribute(uiprivAttrList *alist, uiAttributeType type, size_t start, size_t end);
|
||||
extern void uiprivAttrListRemoveAttributes(uiprivAttrList *alist, size_t start, size_t end);
|
||||
extern void uiprivAttrListRemoveCharacters(uiprivAttrList *alist, size_t start, size_t end);
|
||||
extern void uiprivAttrListForEach(const uiprivAttrList *alist, const uiAttributedString *s, uiAttributedStringForEachAttributeFunc f, void *data);
|
||||
|
||||
// attrstr.c
|
||||
extern const uint16_t *uiprivAttributedStringUTF16String(const uiAttributedString *s);
|
||||
extern size_t uiprivAttributedStringUTF16Len(const uiAttributedString *s);
|
||||
extern size_t uiprivAttributedStringUTF8ToUTF16(const uiAttributedString *s, size_t n);
|
||||
extern size_t *uiprivAttributedStringCopyUTF8ToUTF16Table(const uiAttributedString *s, size_t *n);
|
||||
extern size_t *uiprivAttributedStringCopyUTF16ToUTF8Table(const uiAttributedString *s, size_t *n);
|
||||
|
||||
// per-OS graphemes.c/graphemes.cpp/graphemes.m/etc.
|
||||
typedef struct uiprivGraphemes uiprivGraphemes;
|
||||
struct uiprivGraphemes {
|
||||
size_t len;
|
||||
size_t *pointsToGraphemes;
|
||||
size_t *graphemesToPoints;
|
||||
};
|
||||
extern int uiprivGraphemesTakesUTF16(void);
|
||||
extern uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
101
dep/libui/common/control.c
Normal file
101
dep/libui/common/control.c
Normal file
@@ -0,0 +1,101 @@
|
||||
// 26 may 2015
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiControlDestroy(uiControl *c)
|
||||
{
|
||||
(*(c->Destroy))(c);
|
||||
}
|
||||
|
||||
uintptr_t uiControlHandle(uiControl *c)
|
||||
{
|
||||
return (*(c->Handle))(c);
|
||||
}
|
||||
|
||||
uiControl *uiControlParent(uiControl *c)
|
||||
{
|
||||
return (*(c->Parent))(c);
|
||||
}
|
||||
|
||||
void uiControlSetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
(*(c->SetParent))(c, parent);
|
||||
}
|
||||
|
||||
int uiControlToplevel(uiControl *c)
|
||||
{
|
||||
return (*(c->Toplevel))(c);
|
||||
}
|
||||
|
||||
int uiControlVisible(uiControl *c)
|
||||
{
|
||||
return (*(c->Visible))(c);
|
||||
}
|
||||
|
||||
void uiControlShow(uiControl *c)
|
||||
{
|
||||
(*(c->Show))(c);
|
||||
}
|
||||
|
||||
void uiControlHide(uiControl *c)
|
||||
{
|
||||
(*(c->Hide))(c);
|
||||
}
|
||||
|
||||
int uiControlEnabled(uiControl *c)
|
||||
{
|
||||
return (*(c->Enabled))(c);
|
||||
}
|
||||
|
||||
void uiControlEnable(uiControl *c)
|
||||
{
|
||||
(*(c->Enable))(c);
|
||||
}
|
||||
|
||||
void uiControlDisable(uiControl *c)
|
||||
{
|
||||
(*(c->Disable))(c);
|
||||
}
|
||||
|
||||
#define uiprivControlSignature 0x7569436F
|
||||
|
||||
uiControl *uiAllocControl(size_t size, uint32_t OSsig, uint32_t typesig, const char *typenamestr)
|
||||
{
|
||||
uiControl *c;
|
||||
|
||||
c = (uiControl *) uiprivAlloc(size, typenamestr);
|
||||
c->Signature = uiprivControlSignature;
|
||||
c->OSSignature = OSsig;
|
||||
c->TypeSignature = typesig;
|
||||
return c;
|
||||
}
|
||||
|
||||
void uiFreeControl(uiControl *c)
|
||||
{
|
||||
if (uiControlParent(c) != NULL)
|
||||
uiprivUserBug("You cannot destroy a uiControl while it still has a parent. (control: %p)", c);
|
||||
uiprivFree(c);
|
||||
}
|
||||
|
||||
void uiControlVerifySetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
uiControl *curParent;
|
||||
|
||||
if (uiControlToplevel(c))
|
||||
uiprivUserBug("You cannot give a toplevel uiControl a parent. (control: %p)", c);
|
||||
curParent = uiControlParent(c);
|
||||
if (parent != NULL && curParent != NULL)
|
||||
uiprivUserBug("You cannot give a uiControl a parent while it already has one. (control: %p; current parent: %p; new parent: %p)", c, curParent, parent);
|
||||
if (parent == NULL && curParent == NULL)
|
||||
uiprivImplBug("attempt to double unparent uiControl %p", c);
|
||||
}
|
||||
|
||||
int uiControlEnabledToUser(uiControl *c)
|
||||
{
|
||||
while (c != NULL) {
|
||||
if (!uiControlEnabled(c))
|
||||
return 0;
|
||||
c = uiControlParent(c);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
27
dep/libui/common/controlsigs.h
Normal file
27
dep/libui/common/controlsigs.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// 24 april 2016
|
||||
|
||||
// LONGTERM if I don't decide to remove these outright, should they be renamed uiprivTypeNameSignature? these aren't real symbols, so...
|
||||
|
||||
#define uiAreaSignature 0x41726561
|
||||
#define uiBoxSignature 0x426F784C
|
||||
#define uiButtonSignature 0x42746F6E
|
||||
#define uiCheckboxSignature 0x43686B62
|
||||
#define uiColorButtonSignature 0x436F6C42
|
||||
#define uiComboboxSignature 0x436F6D62
|
||||
#define uiDateTimePickerSignature 0x44545069
|
||||
#define uiEditableComboboxSignature 0x45644362
|
||||
#define uiEntrySignature 0x456E7472
|
||||
#define uiFontButtonSignature 0x466F6E42
|
||||
#define uiFormSignature 0x466F726D
|
||||
#define uiGridSignature 0x47726964
|
||||
#define uiGroupSignature 0x47727062
|
||||
#define uiLabelSignature 0x4C61626C
|
||||
#define uiMultilineEntrySignature 0x4D6C6E45
|
||||
#define uiProgressBarSignature 0x50426172
|
||||
#define uiRadioButtonsSignature 0x5264696F
|
||||
#define uiSeparatorSignature 0x53657061
|
||||
#define uiSliderSignature 0x536C6964
|
||||
#define uiSpinboxSignature 0x5370696E
|
||||
#define uiTabSignature 0x54616273
|
||||
#define uiTableSignature 0x5461626C
|
||||
#define uiWindowSignature 0x57696E64
|
||||
21
dep/libui/common/debug.c
Normal file
21
dep/libui/common/debug.c
Normal file
@@ -0,0 +1,21 @@
|
||||
// 13 may 2016
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
uiprivRealBug(file, line, func, "POSSIBLE IMPLEMENTATION BUG; CONTACT ANDLABS:\n", format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
uiprivRealBug(file, line, func, "You have a bug: ", format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
50
dep/libui/common/matrix.c
Normal file
50
dep/libui/common/matrix.c
Normal file
@@ -0,0 +1,50 @@
|
||||
// 11 october 2015
|
||||
#include <math.h>
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiDrawMatrixSetIdentity(uiDrawMatrix *m)
|
||||
{
|
||||
m->M11 = 1;
|
||||
m->M12 = 0;
|
||||
m->M21 = 0;
|
||||
m->M22 = 1;
|
||||
m->M31 = 0;
|
||||
m->M32 = 0;
|
||||
}
|
||||
|
||||
// The rest of this file provides basic utilities in case the platform doesn't provide any of its own for these tasks.
|
||||
// Keep these as minimal as possible. They should generally not call other fallbacks.
|
||||
|
||||
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ff684171%28v=vs.85%29.aspx#skew_transform
|
||||
// TODO see if there's a way we can avoid the multiplication
|
||||
void uiprivFallbackSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
|
||||
{
|
||||
uiDrawMatrix n;
|
||||
|
||||
uiDrawMatrixSetIdentity(&n);
|
||||
// TODO explain this
|
||||
n.M12 = tan(yamount);
|
||||
n.M21 = tan(xamount);
|
||||
n.M31 = -y * tan(xamount);
|
||||
n.M32 = -x * tan(yamount);
|
||||
uiDrawMatrixMultiply(m, &n);
|
||||
}
|
||||
|
||||
void uiprivScaleCenter(double xCenter, double yCenter, double *x, double *y)
|
||||
{
|
||||
*x = xCenter - (*x * xCenter);
|
||||
*y = yCenter - (*y * yCenter);
|
||||
}
|
||||
|
||||
// the basic algorithm is from cairo
|
||||
// but it's the same algorithm as the transform point, just without M31 and M32 taken into account, so let's just do that instead
|
||||
void uiprivFallbackTransformSize(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
uiDrawMatrix m2;
|
||||
|
||||
m2 = *m;
|
||||
m2.M31 = 0;
|
||||
m2.M32 = 0;
|
||||
uiDrawMatrixTransformPoint(&m2, x, y);
|
||||
}
|
||||
17
dep/libui/common/meson.build
Normal file
17
dep/libui/common/meson.build
Normal file
@@ -0,0 +1,17 @@
|
||||
# 23 march 2019
|
||||
|
||||
libui_sources += [
|
||||
'common/attribute.c',
|
||||
'common/attrlist.c',
|
||||
'common/attrstr.c',
|
||||
'common/areaevents.c',
|
||||
'common/control.c',
|
||||
'common/debug.c',
|
||||
'common/matrix.c',
|
||||
'common/opentype.c',
|
||||
'common/shouldquit.c',
|
||||
'common/tablemodel.c',
|
||||
'common/tablevalue.c',
|
||||
'common/userbugs.c',
|
||||
'common/utf.c',
|
||||
]
|
||||
165
dep/libui/common/opentype.c
Normal file
165
dep/libui/common/opentype.c
Normal file
@@ -0,0 +1,165 @@
|
||||
// 25 february 2018
|
||||
#include <stdlib.h>
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
#include "attrstr.h"
|
||||
|
||||
struct feature {
|
||||
char a;
|
||||
char b;
|
||||
char c;
|
||||
char d;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
struct uiOpenTypeFeatures {
|
||||
struct feature *data;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
};
|
||||
|
||||
#define bytecount(n) ((n) * sizeof (struct feature))
|
||||
|
||||
uiOpenTypeFeatures *uiNewOpenTypeFeatures(void)
|
||||
{
|
||||
uiOpenTypeFeatures *otf;
|
||||
|
||||
otf = uiprivNew(uiOpenTypeFeatures);
|
||||
otf->cap = 16;
|
||||
otf->data = (struct feature *) uiprivAlloc(bytecount(otf->cap), "struct feature[]");
|
||||
otf->len = 0;
|
||||
return otf;
|
||||
}
|
||||
|
||||
void uiFreeOpenTypeFeatures(uiOpenTypeFeatures *otf)
|
||||
{
|
||||
uiprivFree(otf->data);
|
||||
uiprivFree(otf);
|
||||
}
|
||||
|
||||
uiOpenTypeFeatures *uiOpenTypeFeaturesClone(const uiOpenTypeFeatures *otf)
|
||||
{
|
||||
uiOpenTypeFeatures *ret;
|
||||
|
||||
ret = uiprivNew(uiOpenTypeFeatures);
|
||||
ret->len = otf->len;
|
||||
ret->cap = otf->cap;
|
||||
ret->data = (struct feature *) uiprivAlloc(bytecount(ret->cap), "struct feature[]");
|
||||
memset(ret->data, 0, bytecount(ret->cap));
|
||||
memmove(ret->data, otf->data, bytecount(ret->len));
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define intdiff(a, b) (((int) (a)) - ((int) (b)))
|
||||
|
||||
static int featurecmp(const void *a, const void *b)
|
||||
{
|
||||
const struct feature *f = (const struct feature *) a;
|
||||
const struct feature *g = (const struct feature *) b;
|
||||
|
||||
if (f->a != g->a)
|
||||
return intdiff(f->a, g->a);
|
||||
if (f->b != g->b)
|
||||
return intdiff(f->b, g->b);
|
||||
if (f->c != g->c)
|
||||
return intdiff(f->c, g->c);
|
||||
return intdiff(f->d, g->d);
|
||||
}
|
||||
|
||||
static struct feature mkkey(char a, char b, char c, char d)
|
||||
{
|
||||
struct feature f;
|
||||
|
||||
f.a = a;
|
||||
f.b = b;
|
||||
f.c = c;
|
||||
f.d = d;
|
||||
return f;
|
||||
}
|
||||
|
||||
#define find(pkey, otf) bsearch(pkey, otf->data, otf->len, sizeof (struct feature), featurecmp)
|
||||
|
||||
void uiOpenTypeFeaturesAdd(uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value)
|
||||
{
|
||||
struct feature *f;
|
||||
struct feature key;
|
||||
|
||||
// replace existing value if any
|
||||
key = mkkey(a, b, c, d);
|
||||
f = (struct feature *) find(&key, otf);
|
||||
if (f != NULL) {
|
||||
f->value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// if we got here, the tag is new
|
||||
if (otf->len == otf->cap) {
|
||||
otf->cap *= 2;
|
||||
otf->data = (struct feature *) uiprivRealloc(otf->data, bytecount(otf->cap), "struct feature[]");
|
||||
}
|
||||
f = otf->data + otf->len;
|
||||
f->a = a;
|
||||
f->b = b;
|
||||
f->c = c;
|
||||
f->d = d;
|
||||
f->value = value;
|
||||
// LONGTERM qsort here is overkill
|
||||
otf->len++;
|
||||
qsort(otf->data, otf->len, sizeof (struct feature), featurecmp);
|
||||
}
|
||||
|
||||
void uiOpenTypeFeaturesRemove(uiOpenTypeFeatures *otf, char a, char b, char c, char d)
|
||||
{
|
||||
struct feature *f;
|
||||
struct feature key;
|
||||
ptrdiff_t index;
|
||||
size_t count;
|
||||
|
||||
key = mkkey(a, b, c, d);
|
||||
f = (struct feature *) find(&key, otf);
|
||||
if (f == NULL)
|
||||
return;
|
||||
|
||||
index = f - otf->data;
|
||||
count = otf->len - index - 1;
|
||||
memmove(f + 1, f, bytecount(count));
|
||||
otf->len--;
|
||||
}
|
||||
|
||||
int uiOpenTypeFeaturesGet(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t *value)
|
||||
{
|
||||
const struct feature *f;
|
||||
struct feature key;
|
||||
|
||||
key = mkkey(a, b, c, d);
|
||||
f = (const struct feature *) find(&key, otf);
|
||||
if (f == NULL)
|
||||
return 0;
|
||||
*value = f->value;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void uiOpenTypeFeaturesForEach(const uiOpenTypeFeatures *otf, uiOpenTypeFeaturesForEachFunc f, void *data)
|
||||
{
|
||||
size_t n;
|
||||
const struct feature *p;
|
||||
uiForEach ret;
|
||||
|
||||
p = otf->data;
|
||||
for (n = 0; n < otf->len; n++) {
|
||||
ret = (*f)(otf, p->a, p->b, p->c, p->d, p->value, data);
|
||||
// TODO for all: require exact match?
|
||||
if (ret == uiForEachStop)
|
||||
return;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
int uiprivOpenTypeFeaturesEqual(const uiOpenTypeFeatures *a, const uiOpenTypeFeatures *b)
|
||||
{
|
||||
if (a == b)
|
||||
return 1;
|
||||
if (a->len != b->len)
|
||||
return 0;
|
||||
return memcmp(a->data, b->data, bytecount(a->len)) == 0;
|
||||
}
|
||||
22
dep/libui/common/shouldquit.c
Normal file
22
dep/libui/common/shouldquit.c
Normal file
@@ -0,0 +1,22 @@
|
||||
// 9 may 2015
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
static int defaultOnShouldQuit(void *data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int (*onShouldQuit)(void *) = defaultOnShouldQuit;
|
||||
static void *onShouldQuitData = NULL;
|
||||
|
||||
void uiOnShouldQuit(int (*f)(void *), void *data)
|
||||
{
|
||||
onShouldQuit = f;
|
||||
onShouldQuitData = data;
|
||||
}
|
||||
|
||||
int uiprivShouldQuit(void)
|
||||
{
|
||||
return (*onShouldQuit)(onShouldQuitData);
|
||||
}
|
||||
20
dep/libui/common/table.h
Normal file
20
dep/libui/common/table.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// 23 june 2018
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// tablemodel.c
|
||||
extern uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m);
|
||||
extern int uiprivTableModelNumColumns(uiTableModel *m);
|
||||
extern uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column);
|
||||
extern int uiprivTableModelNumRows(uiTableModel *m);
|
||||
extern uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column);
|
||||
extern void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value);
|
||||
extern const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams;
|
||||
extern int uiprivTableModelCellEditable(uiTableModel *m, int row, int column);
|
||||
extern int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
79
dep/libui/common/tablemodel.c
Normal file
79
dep/libui/common/tablemodel.c
Normal file
@@ -0,0 +1,79 @@
|
||||
// 23 june 2018
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
#include "table.h"
|
||||
|
||||
int uiprivTableModelNumColumns(uiTableModel *m)
|
||||
{
|
||||
uiTableModelHandler *mh;
|
||||
|
||||
mh = uiprivTableModelHandler(m);
|
||||
return (*(mh->NumColumns))(mh, m);
|
||||
}
|
||||
|
||||
uiTableValueType uiprivTableModelColumnType(uiTableModel *m, int column)
|
||||
{
|
||||
uiTableModelHandler *mh;
|
||||
|
||||
mh = uiprivTableModelHandler(m);
|
||||
return (*(mh->ColumnType))(mh, m, column);
|
||||
}
|
||||
|
||||
int uiprivTableModelNumRows(uiTableModel *m)
|
||||
{
|
||||
uiTableModelHandler *mh;
|
||||
|
||||
mh = uiprivTableModelHandler(m);
|
||||
return (*(mh->NumRows))(mh, m);
|
||||
}
|
||||
|
||||
uiTableValue *uiprivTableModelCellValue(uiTableModel *m, int row, int column)
|
||||
{
|
||||
uiTableModelHandler *mh;
|
||||
|
||||
mh = uiprivTableModelHandler(m);
|
||||
return (*(mh->CellValue))(mh, m, row, column);
|
||||
}
|
||||
|
||||
void uiprivTableModelSetCellValue(uiTableModel *m, int row, int column, const uiTableValue *value)
|
||||
{
|
||||
uiTableModelHandler *mh;
|
||||
|
||||
mh = uiprivTableModelHandler(m);
|
||||
(*(mh->SetCellValue))(mh, m, row, column, value);
|
||||
}
|
||||
|
||||
const uiTableTextColumnOptionalParams uiprivDefaultTextColumnOptionalParams = {
|
||||
.ColorModelColumn = -1,
|
||||
};
|
||||
|
||||
int uiprivTableModelCellEditable(uiTableModel *m, int row, int column)
|
||||
{
|
||||
uiTableValue *value;
|
||||
int editable;
|
||||
|
||||
switch (column) {
|
||||
case uiTableModelColumnNeverEditable:
|
||||
return 0;
|
||||
case uiTableModelColumnAlwaysEditable:
|
||||
return 1;
|
||||
}
|
||||
value = uiprivTableModelCellValue(m, row, column);
|
||||
editable = uiTableValueInt(value);
|
||||
uiFreeTableValue(value);
|
||||
return editable;
|
||||
}
|
||||
|
||||
int uiprivTableModelColorIfProvided(uiTableModel *m, int row, int column, double *r, double *g, double *b, double *a)
|
||||
{
|
||||
uiTableValue *value;
|
||||
|
||||
if (column == -1)
|
||||
return 0;
|
||||
value = uiprivTableModelCellValue(m, row, column);
|
||||
if (value == NULL)
|
||||
return 0;
|
||||
uiTableValueColor(value, r, g, b, a);
|
||||
uiFreeTableValue(value);
|
||||
return 1;
|
||||
}
|
||||
106
dep/libui/common/tablevalue.c
Normal file
106
dep/libui/common/tablevalue.c
Normal file
@@ -0,0 +1,106 @@
|
||||
// 3 june 2018
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
#include "table.h"
|
||||
|
||||
struct uiTableValue {
|
||||
uiTableValueType type;
|
||||
union {
|
||||
char *str;
|
||||
uiImage *img;
|
||||
int i;
|
||||
struct {
|
||||
double r;
|
||||
double g;
|
||||
double b;
|
||||
double a;
|
||||
} color;
|
||||
} u;
|
||||
};
|
||||
|
||||
static uiTableValue *newTableValue(uiTableValueType type)
|
||||
{
|
||||
uiTableValue *v;
|
||||
|
||||
v = uiprivNew(uiTableValue);
|
||||
v->type = type;
|
||||
return v;
|
||||
}
|
||||
|
||||
void uiFreeTableValue(uiTableValue *v)
|
||||
{
|
||||
switch (v->type) {
|
||||
case uiTableValueTypeString:
|
||||
uiprivFree(v->u.str);
|
||||
break;
|
||||
}
|
||||
uiprivFree(v);
|
||||
}
|
||||
|
||||
uiTableValueType uiTableValueGetType(const uiTableValue *v)
|
||||
{
|
||||
return v->type;
|
||||
}
|
||||
|
||||
uiTableValue *uiNewTableValueString(const char *str)
|
||||
{
|
||||
uiTableValue *v;
|
||||
|
||||
v = newTableValue(uiTableValueTypeString);
|
||||
v->u.str = (char *) uiprivAlloc((strlen(str) + 1) * sizeof (char), "char[] (uiTableValue)");
|
||||
strcpy(v->u.str, str);
|
||||
return v;
|
||||
}
|
||||
|
||||
const char *uiTableValueString(const uiTableValue *v)
|
||||
{
|
||||
return v->u.str;
|
||||
}
|
||||
|
||||
uiTableValue *uiNewTableValueImage(uiImage *img)
|
||||
{
|
||||
uiTableValue *v;
|
||||
|
||||
v = newTableValue(uiTableValueTypeImage);
|
||||
v->u.img = img;
|
||||
return v;
|
||||
}
|
||||
|
||||
uiImage *uiTableValueImage(const uiTableValue *v)
|
||||
{
|
||||
return v->u.img;
|
||||
}
|
||||
|
||||
uiTableValue *uiNewTableValueInt(int i)
|
||||
{
|
||||
uiTableValue *v;
|
||||
|
||||
v = newTableValue(uiTableValueTypeInt);
|
||||
v->u.i = i;
|
||||
return v;
|
||||
}
|
||||
|
||||
int uiTableValueInt(const uiTableValue *v)
|
||||
{
|
||||
return v->u.i;
|
||||
}
|
||||
|
||||
uiTableValue *uiNewTableValueColor(double r, double g, double b, double a)
|
||||
{
|
||||
uiTableValue *v;
|
||||
|
||||
v = newTableValue(uiTableValueTypeColor);
|
||||
v->u.color.r = r;
|
||||
v->u.color.g = g;
|
||||
v->u.color.b = b;
|
||||
v->u.color.a = a;
|
||||
return v;
|
||||
}
|
||||
|
||||
void uiTableValueColor(const uiTableValue *v, double *r, double *g, double *b, double *a)
|
||||
{
|
||||
*r = v->u.color.r;
|
||||
*g = v->u.color.g;
|
||||
*b = v->u.color.b;
|
||||
*a = v->u.color.a;
|
||||
}
|
||||
67
dep/libui/common/uipriv.h
Normal file
67
dep/libui/common/uipriv.h
Normal file
@@ -0,0 +1,67 @@
|
||||
// 6 april 2015
|
||||
// note: this file should not include ui.h, as the OS-specific ui_*.h files are included between that one and this one in the OS-specific uipriv_*.h* files
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include "controlsigs.h"
|
||||
#include "utf.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// OS-specific init.* or main.* files
|
||||
extern uiInitOptions uiprivOptions;
|
||||
|
||||
// OS-specific alloc.* files
|
||||
extern void *uiprivAlloc(size_t, const char *);
|
||||
#define uiprivNew(T) ((T *) uiprivAlloc(sizeof (T), #T))
|
||||
extern void *uiprivRealloc(void *, size_t, const char *);
|
||||
extern void uiprivFree(void *);
|
||||
|
||||
// debug.c and OS-specific debug.* files
|
||||
// TODO get rid of this mess...
|
||||
// ugh, __func__ was only introduced in MSVC 2015...
|
||||
#ifdef _MSC_VER
|
||||
#define uiprivMacro__func__ __FUNCTION__
|
||||
#else
|
||||
#define uiprivMacro__func__ __func__
|
||||
#endif
|
||||
extern void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap);
|
||||
#define uiprivMacro_ns2(s) #s
|
||||
#define uiprivMacro_ns(s) uiprivMacro_ns2(s)
|
||||
extern void uiprivDoImplBug(const char *file, const char *line, const char *func, const char *format, ...);
|
||||
#define uiprivImplBug(...) uiprivDoImplBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__)
|
||||
extern void uiprivDoUserBug(const char *file, const char *line, const char *func, const char *format, ...);
|
||||
#define uiprivUserBug(...) uiprivDoUserBug(__FILE__, uiprivMacro_ns(__LINE__), uiprivMacro__func__, __VA_ARGS__)
|
||||
|
||||
// shouldquit.c
|
||||
extern int uiprivShouldQuit(void);
|
||||
|
||||
// areaevents.c
|
||||
typedef struct uiprivClickCounter uiprivClickCounter;
|
||||
// you should call Reset() to zero-initialize a new instance
|
||||
// it doesn't matter that all the non-count fields are zero: the first click will fail the curButton test straightaway, so it'll return 1 and set the rest of the structure accordingly
|
||||
struct uiprivClickCounter {
|
||||
int curButton;
|
||||
int rectX0;
|
||||
int rectY0;
|
||||
int rectX1;
|
||||
int rectY1;
|
||||
uintptr_t prevTime;
|
||||
int count;
|
||||
};
|
||||
extern int uiprivClickCounterClick(uiprivClickCounter *c, int button, int x, int y, uintptr_t time, uintptr_t maxTime, int32_t xdist, int32_t ydist);
|
||||
extern void uiprivClickCounterReset(uiprivClickCounter *);
|
||||
extern int uiprivFromScancode(uintptr_t, uiAreaKeyEvent *);
|
||||
|
||||
// matrix.c
|
||||
extern void uiprivFallbackSkew(uiDrawMatrix *, double, double, double, double);
|
||||
extern void uiprivScaleCenter(double, double, double *, double *);
|
||||
extern void uiprivFallbackTransformSize(uiDrawMatrix *, double *, double *);
|
||||
|
||||
// OS-specific text.* files
|
||||
extern int uiprivStricmp(const char *a, const char *b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
8
dep/libui/common/userbugs.c
Normal file
8
dep/libui/common/userbugs.c
Normal file
@@ -0,0 +1,8 @@
|
||||
// 22 may 2016
|
||||
#include "../ui.h"
|
||||
#include "uipriv.h"
|
||||
|
||||
void uiUserBugCannotSetParentOnToplevel(const char *type)
|
||||
{
|
||||
uiprivUserBug("You cannot make a %s a child of another uiControl,", type);
|
||||
}
|
||||
348
dep/libui/common/utf.c
Normal file
348
dep/libui/common/utf.c
Normal file
@@ -0,0 +1,348 @@
|
||||
// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/
|
||||
// 10 november 2016
|
||||
// function names have been altered to avoid namespace collisions in libui static builds (see utf.h)
|
||||
#include "utf.h"
|
||||
|
||||
// this code imitates Go's unicode/utf8 and unicode/utf16
|
||||
// the biggest difference is that a rune is unsigned instead of signed (because Go guarantees what a right shift on a signed number will do, whereas C does not)
|
||||
// it is also an imitation so we can license it under looser terms than the Go source
|
||||
#define badrune 0xFFFD
|
||||
|
||||
// encoded must be at most 4 bytes
|
||||
// TODO clean this code up somehow
|
||||
size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded)
|
||||
{
|
||||
uint8_t b, c, d, e;
|
||||
size_t n;
|
||||
|
||||
// not in the valid range for Unicode
|
||||
if (rune > 0x10FFFF)
|
||||
rune = badrune;
|
||||
// surrogate runes cannot be encoded
|
||||
if (rune >= 0xD800 && rune < 0xE000)
|
||||
rune = badrune;
|
||||
|
||||
if (rune < 0x80) { // ASCII bytes represent themselves
|
||||
b = (uint8_t) (rune & 0xFF);
|
||||
n = 1;
|
||||
goto done;
|
||||
}
|
||||
if (rune < 0x800) { // two-byte encoding
|
||||
c = (uint8_t) (rune & 0x3F);
|
||||
c |= 0x80;
|
||||
rune >>= 6;
|
||||
b = (uint8_t) (rune & 0x1F);
|
||||
b |= 0xC0;
|
||||
n = 2;
|
||||
goto done;
|
||||
}
|
||||
if (rune < 0x10000) { // three-byte encoding
|
||||
d = (uint8_t) (rune & 0x3F);
|
||||
d |= 0x80;
|
||||
rune >>= 6;
|
||||
c = (uint8_t) (rune & 0x3F);
|
||||
c |= 0x80;
|
||||
rune >>= 6;
|
||||
b = (uint8_t) (rune & 0x0F);
|
||||
b |= 0xE0;
|
||||
n = 3;
|
||||
goto done;
|
||||
}
|
||||
// otherwise use a four-byte encoding
|
||||
e = (uint8_t) (rune & 0x3F);
|
||||
e |= 0x80;
|
||||
rune >>= 6;
|
||||
d = (uint8_t) (rune & 0x3F);
|
||||
d |= 0x80;
|
||||
rune >>= 6;
|
||||
c = (uint8_t) (rune & 0x3F);
|
||||
c |= 0x80;
|
||||
rune >>= 6;
|
||||
b = (uint8_t) (rune & 0x07);
|
||||
b |= 0xF0;
|
||||
n = 4;
|
||||
|
||||
done:
|
||||
encoded[0] = b;
|
||||
if (n > 1)
|
||||
encoded[1] = c;
|
||||
if (n > 2)
|
||||
encoded[2] = d;
|
||||
if (n > 3)
|
||||
encoded[3] = e;
|
||||
return n;
|
||||
}
|
||||
|
||||
const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune)
|
||||
{
|
||||
uint8_t b, c;
|
||||
uint8_t lowestAllowed, highestAllowed;
|
||||
size_t i, expected;
|
||||
int bad;
|
||||
|
||||
b = (uint8_t) (*s);
|
||||
if (b < 0x80) { // ASCII bytes represent themselves
|
||||
*rune = b;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
// 0xC0 and 0xC1 cover 2-byte overlong equivalents
|
||||
// 0xF5 to 0xFD cover values > 0x10FFFF
|
||||
// 0xFE and 0xFF were never defined (always illegal)
|
||||
if (b < 0xC2 || b > 0xF4) { // invalid
|
||||
*rune = badrune;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
// this determines the range of allowed first continuation bytes
|
||||
lowestAllowed = 0x80;
|
||||
highestAllowed = 0xBF;
|
||||
switch (b) {
|
||||
case 0xE0:
|
||||
// disallow 3-byte overlong equivalents
|
||||
lowestAllowed = 0xA0;
|
||||
break;
|
||||
case 0xED:
|
||||
// disallow surrogate characters
|
||||
highestAllowed = 0x9F;
|
||||
break;
|
||||
case 0xF0:
|
||||
// disallow 4-byte overlong equivalents
|
||||
lowestAllowed = 0x90;
|
||||
break;
|
||||
case 0xF4:
|
||||
// disallow values > 0x10FFFF
|
||||
highestAllowed = 0x8F;
|
||||
break;
|
||||
}
|
||||
|
||||
// and this determines how many continuation bytes are expected
|
||||
expected = 1;
|
||||
if (b >= 0xE0)
|
||||
expected++;
|
||||
if (b >= 0xF0)
|
||||
expected++;
|
||||
if (nElem != 0) { // are there enough bytes?
|
||||
nElem--;
|
||||
if (nElem < expected) { // nope
|
||||
*rune = badrune;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
// ensure that everything is correct
|
||||
// if not, **only** consume the initial byte
|
||||
bad = 0;
|
||||
for (i = 0; i < expected; i++) {
|
||||
c = (uint8_t) (s[1 + i]);
|
||||
if (c < lowestAllowed || c > highestAllowed) {
|
||||
bad = 1;
|
||||
break;
|
||||
}
|
||||
// the old lowestAllowed and highestAllowed is only for the first continuation byte
|
||||
lowestAllowed = 0x80;
|
||||
highestAllowed = 0xBF;
|
||||
}
|
||||
if (bad) {
|
||||
*rune = badrune;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
// now do the topmost bits
|
||||
if (b < 0xE0)
|
||||
*rune = b & 0x1F;
|
||||
else if (b < 0xF0)
|
||||
*rune = b & 0x0F;
|
||||
else
|
||||
*rune = b & 0x07;
|
||||
s++; // we can finally move on
|
||||
|
||||
// now do the continuation bytes
|
||||
for (; expected; expected--) {
|
||||
c = (uint8_t) (*s);
|
||||
s++;
|
||||
c &= 0x3F; // strip continuation bits
|
||||
*rune <<= 6;
|
||||
*rune |= c;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// encoded must have at most 2 elements
|
||||
size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded)
|
||||
{
|
||||
uint16_t low, high;
|
||||
|
||||
// not in the valid range for Unicode
|
||||
if (rune > 0x10FFFF)
|
||||
rune = badrune;
|
||||
// surrogate runes cannot be encoded
|
||||
if (rune >= 0xD800 && rune < 0xE000)
|
||||
rune = badrune;
|
||||
|
||||
if (rune < 0x10000) {
|
||||
encoded[0] = (uint16_t) rune;
|
||||
return 1;
|
||||
}
|
||||
|
||||
rune -= 0x10000;
|
||||
low = (uint16_t) (rune & 0x3FF);
|
||||
rune >>= 10;
|
||||
high = (uint16_t) (rune & 0x3FF);
|
||||
encoded[0] = high | 0xD800;
|
||||
encoded[1] = low | 0xDC00;
|
||||
return 2;
|
||||
}
|
||||
|
||||
// TODO see if this can be cleaned up somehow
|
||||
const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune)
|
||||
{
|
||||
uint16_t high, low;
|
||||
|
||||
if (*s < 0xD800 || *s >= 0xE000) {
|
||||
// self-representing character
|
||||
*rune = *s;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
if (*s >= 0xDC00) {
|
||||
// out-of-order surrogates
|
||||
*rune = badrune;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
if (nElem == 1) { // not enough elements
|
||||
*rune = badrune;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
high = *s;
|
||||
high &= 0x3FF;
|
||||
if (s[1] < 0xDC00 || s[1] >= 0xE000) {
|
||||
// bad surrogate pair
|
||||
*rune = badrune;
|
||||
s++;
|
||||
return s;
|
||||
}
|
||||
s++;
|
||||
low = *s;
|
||||
s++;
|
||||
low &= 0x3FF;
|
||||
*rune = high;
|
||||
*rune <<= 10;
|
||||
*rune |= low;
|
||||
*rune += 0x10000;
|
||||
return s;
|
||||
}
|
||||
|
||||
// TODO find a way to reduce the code in all of these somehow
|
||||
// TODO find a way to remove u as well
|
||||
size_t uiprivUTF8RuneCount(const char *s, size_t nElem)
|
||||
{
|
||||
size_t len;
|
||||
uint32_t rune;
|
||||
|
||||
if (nElem != 0) {
|
||||
const char *t, *u;
|
||||
|
||||
len = 0;
|
||||
t = s;
|
||||
while (nElem != 0) {
|
||||
u = uiprivUTF8DecodeRune(t, nElem, &rune);
|
||||
len++;
|
||||
nElem -= u - t;
|
||||
t = u;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
len = 0;
|
||||
while (*s) {
|
||||
s = uiprivUTF8DecodeRune(s, nElem, &rune);
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t uiprivUTF8UTF16Count(const char *s, size_t nElem)
|
||||
{
|
||||
size_t len;
|
||||
uint32_t rune;
|
||||
uint16_t encoded[2];
|
||||
|
||||
if (nElem != 0) {
|
||||
const char *t, *u;
|
||||
|
||||
len = 0;
|
||||
t = s;
|
||||
while (nElem != 0) {
|
||||
u = uiprivUTF8DecodeRune(t, nElem, &rune);
|
||||
len += uiprivUTF16EncodeRune(rune, encoded);
|
||||
nElem -= u - t;
|
||||
t = u;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
len = 0;
|
||||
while (*s) {
|
||||
s = uiprivUTF8DecodeRune(s, nElem, &rune);
|
||||
len += uiprivUTF16EncodeRune(rune, encoded);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem)
|
||||
{
|
||||
size_t len;
|
||||
uint32_t rune;
|
||||
|
||||
if (nElem != 0) {
|
||||
const uint16_t *t, *u;
|
||||
|
||||
len = 0;
|
||||
t = s;
|
||||
while (nElem != 0) {
|
||||
u = uiprivUTF16DecodeRune(t, nElem, &rune);
|
||||
len++;
|
||||
nElem -= u - t;
|
||||
t = u;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
len = 0;
|
||||
while (*s) {
|
||||
s = uiprivUTF16DecodeRune(s, nElem, &rune);
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem)
|
||||
{
|
||||
size_t len;
|
||||
uint32_t rune;
|
||||
char encoded[4];
|
||||
|
||||
if (nElem != 0) {
|
||||
const uint16_t *t, *u;
|
||||
|
||||
len = 0;
|
||||
t = s;
|
||||
while (nElem != 0) {
|
||||
u = uiprivUTF16DecodeRune(t, nElem, &rune);
|
||||
len += uiprivUTF8EncodeRune(rune, encoded);
|
||||
nElem -= u - t;
|
||||
t = u;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
len = 0;
|
||||
while (*s) {
|
||||
s = uiprivUTF16DecodeRune(s, nElem, &rune);
|
||||
len += uiprivUTF8EncodeRune(rune, encoded);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
105
dep/libui/common/utf.h
Normal file
105
dep/libui/common/utf.h
Normal file
@@ -0,0 +1,105 @@
|
||||
// utf by pietro gagliardi (andlabs) — https://github.com/andlabs/utf/
|
||||
// 10 november 2016
|
||||
|
||||
// note the overridden names with uipriv at the beginning; this avoids potential symbol clashes when building libui as a static library
|
||||
// LONGTERM find a way to encode the name overrides directly into the utf library
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// TODO (for utf itself as well) should this go outside the extern "C" block or not
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// if nElem == 0, assume the buffer has no upper limit and is '\0' terminated
|
||||
// otherwise, assume buffer is NOT '\0' terminated but is bounded by nElem *elements*
|
||||
|
||||
extern size_t uiprivUTF8EncodeRune(uint32_t rune, char *encoded);
|
||||
extern const char *uiprivUTF8DecodeRune(const char *s, size_t nElem, uint32_t *rune);
|
||||
extern size_t uiprivUTF16EncodeRune(uint32_t rune, uint16_t *encoded);
|
||||
extern const uint16_t *uiprivUTF16DecodeRune(const uint16_t *s, size_t nElem, uint32_t *rune);
|
||||
|
||||
extern size_t uiprivUTF8RuneCount(const char *s, size_t nElem);
|
||||
extern size_t uiprivUTF8UTF16Count(const char *s, size_t nElem);
|
||||
extern size_t uiprivUTF16RuneCount(const uint16_t *s, size_t nElem);
|
||||
extern size_t uiprivUTF16UTF8Count(const uint16_t *s, size_t nElem);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
// TODO sync this back to upstream (need copyright clearance first)
|
||||
|
||||
// On Windows, wchar_t is equivalent to uint16_t, but C++ requires
|
||||
// wchar_t to be a completely distinct type. These overloads allow
|
||||
// passing wchar_t pointers directly into these functions from C++
|
||||
// on Windows. Otherwise, you'd need to cast to pass a wchar_t
|
||||
// pointer, WCHAR pointer, or equivalent to these functions.
|
||||
//
|
||||
// This does not apply to MSVC because the situation there is
|
||||
// slightly more complicated; see below.
|
||||
#if defined(_WIN32) && !defined(_MSC_VER)
|
||||
|
||||
inline size_t uiprivUTF16EncodeRune(uint32_t rune, wchar_t *encoded)
|
||||
{
|
||||
return uiprivUTF16EncodeRune(rune, reinterpret_cast<uint16_t *>(encoded));
|
||||
}
|
||||
|
||||
inline const wchar_t *uiprivUTF16DecodeRune(const wchar_t *s, size_t nElem, uint32_t *rune)
|
||||
{
|
||||
const uint16_t *ret;
|
||||
|
||||
ret = uiprivUTF16DecodeRune(reinterpret_cast<const uint16_t *>(s), nElem, rune);
|
||||
return reinterpret_cast<const wchar_t *>(ret);
|
||||
}
|
||||
|
||||
inline size_t uiprivUTF16RuneCount(const wchar_t *s, size_t nElem)
|
||||
{
|
||||
return uiprivUTF16RuneCount(reinterpret_cast<const uint16_t *>(s), nElem);
|
||||
}
|
||||
|
||||
inline size_t uiprivUTF16UTF8Count(const wchar_t *s, size_t nElem)
|
||||
{
|
||||
return uiprivUTF16UTF8Count(reinterpret_cast<const uint16_t *>(s), nElem);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// This is the same as the above, except that with MSVC, whether
|
||||
// wchar_t is a keyword or not is controlled by a compiler option!
|
||||
// (At least with gcc, this is not the case; thanks redi in
|
||||
// irc.freenode.net/#gcc.) We use __wchar_t to be independent of
|
||||
// the option; see https://blogs.msdn.microsoft.com/oldnewthing/20161201-00/?p=94836
|
||||
// (ironically posted one day after I initially wrote this code!).
|
||||
// TODO should defined(_WIN32) be used too?
|
||||
// TODO check this under /Wall
|
||||
// TODO are C-style casts enough? or will that fail in /Wall?
|
||||
// TODO same for UniChar/unichar on Mac? if both are unsigned then we have nothing to worry about
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
inline size_t uiprivUTF16EncodeRune(uint32_t rune, __wchar_t *encoded)
|
||||
{
|
||||
return uiprivUTF16EncodeRune(rune, reinterpret_cast<uint16_t *>(encoded));
|
||||
}
|
||||
|
||||
inline const __wchar_t *uiprivUTF16DecodeRune(const __wchar_t *s, size_t nElem, uint32_t *rune)
|
||||
{
|
||||
const uint16_t *ret;
|
||||
|
||||
ret = uiprivUTF16DecodeRune(reinterpret_cast<const uint16_t *>(s), nElem, rune);
|
||||
return reinterpret_cast<const __wchar_t *>(ret);
|
||||
}
|
||||
|
||||
inline size_t uiprivUTF16RuneCount(const __wchar_t *s, size_t nElem)
|
||||
{
|
||||
return uiprivUTF16RuneCount(reinterpret_cast<const uint16_t *>(s), nElem);
|
||||
}
|
||||
|
||||
inline size_t uiprivUTF16UTF8Count(const __wchar_t *s, size_t nElem)
|
||||
{
|
||||
return uiprivUTF16UTF8Count(reinterpret_cast<const uint16_t *>(s), nElem);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
39
dep/libui/darwin/OLD_table.m
Normal file
39
dep/libui/darwin/OLD_table.m
Normal file
@@ -0,0 +1,39 @@
|
||||
// 21 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODOs
|
||||
// - header cell seems off
|
||||
// - background color shows up for a line or two below selection
|
||||
// - editable NSTextFields have no intrinsic width
|
||||
// - is the Y position of checkbox cells correct?
|
||||
|
||||
@implementation tablePart
|
||||
|
||||
- (NSView *)mkView:(uiTableModel *)m row:(int)row
|
||||
{
|
||||
// if stretchy, don't hug, otherwise hug forcibly
|
||||
if (self.expand)
|
||||
[view setContentHuggingPriority:NSLayoutPriorityDefaultLow forOrientation:NSLayoutConstraintOrientationHorizontal];
|
||||
else
|
||||
[view setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiTableColumn *uiTableAppendColumn(uiTable *t, const char *name)
|
||||
{
|
||||
uiTableColumn *c;
|
||||
|
||||
c = uiprivNew(uiTableColumn);
|
||||
c->c = [[tableColumn alloc] initWithIdentifier:@""];
|
||||
c->c.libui_col = c;
|
||||
// via Interface Builder
|
||||
[c->c setResizingMask:(NSTableColumnAutoresizingMask | NSTableColumnUserResizingMask)];
|
||||
// 10.10 adds -[NSTableColumn setTitle:]; before then we have to do this
|
||||
[[c->c headerCell] setStringValue:uiprivToNSString(name)];
|
||||
// TODO is this sufficient?
|
||||
[[c->c headerCell] setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
|
||||
c->parts = [NSMutableArray new];
|
||||
[t->tv addTableColumn:c->c];
|
||||
return c;
|
||||
}
|
||||
403
dep/libui/darwin/aat.m
Normal file
403
dep/libui/darwin/aat.m
Normal file
@@ -0,0 +1,403 @@
|
||||
// 14 february 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// TODO explain the purpose of this file
|
||||
|
||||
static void boolspec(uint32_t value, uint16_t type, uint16_t ifTrue, uint16_t ifFalse, uiprivAATBlock f)
|
||||
{
|
||||
// TODO are values other than 1 accepted for true by OpenType itself? (same for the rest of the file)
|
||||
if (value != 0) {
|
||||
f(type, ifTrue);
|
||||
return;
|
||||
}
|
||||
f(type, ifFalse);
|
||||
}
|
||||
|
||||
// TODO remove the need for this
|
||||
// TODO remove x8tox32()
|
||||
#define x8tox32(x) ((uint32_t) (((uint8_t) (x)) & 0xFF))
|
||||
#define mkTag(a, b, c, d) \
|
||||
((x8tox32(a) << 24) | \
|
||||
(x8tox32(b) << 16) | \
|
||||
(x8tox32(c) << 8) | \
|
||||
x8tox32(d))
|
||||
|
||||
// TODO double-check drawtext example to make sure all of these are used properly (I already screwed dlig up by putting clig twice instead)
|
||||
void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f)
|
||||
{
|
||||
switch (mkTag(a, b, c, d)) {
|
||||
case mkTag('l', 'i', 'g', 'a'):
|
||||
boolspec(value, kLigaturesType,
|
||||
kCommonLigaturesOnSelector,
|
||||
kCommonLigaturesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('r', 'l', 'i', 'g'):
|
||||
boolspec(value, kLigaturesType,
|
||||
kRequiredLigaturesOnSelector,
|
||||
kRequiredLigaturesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('d', 'l', 'i', 'g'):
|
||||
boolspec(value, kLigaturesType,
|
||||
kRareLigaturesOnSelector,
|
||||
kRareLigaturesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('c', 'l', 'i', 'g'):
|
||||
boolspec(value, kLigaturesType,
|
||||
kContextualLigaturesOnSelector,
|
||||
kContextualLigaturesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('h', 'l', 'i', 'g'):
|
||||
// This technically isn't what is meant by "historical ligatures", but Core Text's internal AAT-to-OpenType mapping says to include it, so we include it too
|
||||
case mkTag('h', 'i', 's', 't'):
|
||||
boolspec(value, kLigaturesType,
|
||||
kHistoricalLigaturesOnSelector,
|
||||
kHistoricalLigaturesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('u', 'n', 'i', 'c'):
|
||||
// TODO is this correct, or should we provide an else case?
|
||||
if (value != 0)
|
||||
// this is undocumented; it comes from Core Text's internal AAT-to-OpenType conversion table
|
||||
f(kLetterCaseType, 14);
|
||||
break;
|
||||
|
||||
// TODO will the following handle all cases properly, or are elses going to be needed?
|
||||
case mkTag('p', 'n', 'u', 'm'):
|
||||
if (value != 0)
|
||||
f(kNumberSpacingType, kProportionalNumbersSelector);
|
||||
break;
|
||||
case mkTag('t', 'n', 'u', 'm'):
|
||||
if (value != 0)
|
||||
f(kNumberSpacingType, kMonospacedNumbersSelector);
|
||||
break;
|
||||
|
||||
// TODO will the following handle all cases properly, or are elses going to be needed?
|
||||
case mkTag('s', 'u', 'p', 's'):
|
||||
if (value != 0)
|
||||
f(kVerticalPositionType, kSuperiorsSelector);
|
||||
break;
|
||||
case mkTag('s', 'u', 'b', 's'):
|
||||
if (value != 0)
|
||||
f(kVerticalPositionType, kInferiorsSelector);
|
||||
break;
|
||||
case mkTag('o', 'r', 'd', 'n'):
|
||||
if (value != 0)
|
||||
f(kVerticalPositionType, kOrdinalsSelector);
|
||||
break;
|
||||
case mkTag('s', 'i', 'n', 'f'):
|
||||
if (value != 0)
|
||||
f(kVerticalPositionType, kScientificInferiorsSelector);
|
||||
break;
|
||||
|
||||
// TODO will the following handle all cases properly, or are elses going to be needed?
|
||||
case mkTag('a', 'f', 'r', 'c'):
|
||||
if (value != 0)
|
||||
f(kFractionsType, kVerticalFractionsSelector);
|
||||
break;
|
||||
case mkTag('f', 'r', 'a', 'c'):
|
||||
if (value != 0)
|
||||
f(kFractionsType, kDiagonalFractionsSelector);
|
||||
break;
|
||||
|
||||
case mkTag('z', 'e', 'r', 'o'):
|
||||
boolspec(value, kTypographicExtrasType,
|
||||
kSlashedZeroOnSelector,
|
||||
kSlashedZeroOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('m', 'g', 'r', 'k'):
|
||||
boolspec(value, kMathematicalExtrasType,
|
||||
kMathematicalGreekOnSelector,
|
||||
kMathematicalGreekOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('o', 'r', 'n', 'm'):
|
||||
f(kOrnamentSetsType, (uint16_t) value);
|
||||
break;
|
||||
case mkTag('a', 'a', 'l', 't'):
|
||||
f(kCharacterAlternativesType, (uint16_t) value);
|
||||
break;
|
||||
case mkTag('t', 'i', 't', 'l'):
|
||||
// TODO is this correct, or should we provide an else case?
|
||||
if (value != 0)
|
||||
f(kStyleOptionsType, kTitlingCapsSelector);
|
||||
break;
|
||||
|
||||
// TODO will the following handle all cases properly, or are elses going to be needed?
|
||||
case mkTag('t', 'r', 'a', 'd'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kTraditionalCharactersSelector);
|
||||
break;
|
||||
case mkTag('s', 'm', 'p', 'l'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kSimplifiedCharactersSelector);
|
||||
break;
|
||||
case mkTag('j', 'p', '7', '8'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kJIS1978CharactersSelector);
|
||||
break;
|
||||
case mkTag('j', 'p', '8', '3'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kJIS1983CharactersSelector);
|
||||
break;
|
||||
case mkTag('j', 'p', '9', '0'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kJIS1990CharactersSelector);
|
||||
break;
|
||||
case mkTag('e', 'x', 'p', 't'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kExpertCharactersSelector);
|
||||
break;
|
||||
case mkTag('j', 'p', '0', '4'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kJIS2004CharactersSelector);
|
||||
break;
|
||||
case mkTag('h', 'o', 'j', 'o'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kHojoCharactersSelector);
|
||||
break;
|
||||
case mkTag('n', 'l', 'c', 'k'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kNLCCharactersSelector);
|
||||
break;
|
||||
case mkTag('t', 'n', 'a', 'm'):
|
||||
if (value != 0)
|
||||
f(kCharacterShapeType, kTraditionalNamesCharactersSelector);
|
||||
break;
|
||||
|
||||
case mkTag('o', 'n', 'u', 'm'):
|
||||
// Core Text's internal AAT-to-OpenType mapping says to include this, so we include it too
|
||||
// TODO is it always set?
|
||||
case mkTag('l', 'n', 'u', 'm'):
|
||||
// TODO is this correct, or should we provide an else case?
|
||||
if (value != 0)
|
||||
f(kNumberCaseType, kLowerCaseNumbersSelector);
|
||||
break;
|
||||
case mkTag('h', 'n', 'g', 'l'):
|
||||
// TODO is this correct, or should we provide an else case?
|
||||
if (value != 0)
|
||||
f(kTransliterationType, kHanjaToHangulSelector);
|
||||
break;
|
||||
case mkTag('n', 'a', 'l', 't'):
|
||||
f(kAnnotationType, (uint16_t) value);
|
||||
break;
|
||||
case mkTag('r', 'u', 'b', 'y'):
|
||||
// include this for completeness
|
||||
boolspec(value, kRubyKanaType,
|
||||
kRubyKanaSelector,
|
||||
kNoRubyKanaSelector,
|
||||
f);
|
||||
// this is the current one
|
||||
boolspec(value, kRubyKanaType,
|
||||
kRubyKanaOnSelector,
|
||||
kRubyKanaOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('i', 't', 'a', 'l'):
|
||||
// include this for completeness
|
||||
boolspec(value, kItalicCJKRomanType,
|
||||
kCJKItalicRomanSelector,
|
||||
kNoCJKItalicRomanSelector,
|
||||
f);
|
||||
// this is the current one
|
||||
boolspec(value, kItalicCJKRomanType,
|
||||
kCJKItalicRomanOnSelector,
|
||||
kCJKItalicRomanOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('c', 'a', 's', 'e'):
|
||||
boolspec(value, kCaseSensitiveLayoutType,
|
||||
kCaseSensitiveLayoutOnSelector,
|
||||
kCaseSensitiveLayoutOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('c', 'p', 's', 'p'):
|
||||
boolspec(value, kCaseSensitiveLayoutType,
|
||||
kCaseSensitiveSpacingOnSelector,
|
||||
kCaseSensitiveSpacingOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('h', 'k', 'n', 'a'):
|
||||
boolspec(value, kAlternateKanaType,
|
||||
kAlternateHorizKanaOnSelector,
|
||||
kAlternateHorizKanaOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('v', 'k', 'n', 'a'):
|
||||
boolspec(value, kAlternateKanaType,
|
||||
kAlternateVertKanaOnSelector,
|
||||
kAlternateVertKanaOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '1'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltOneOnSelector,
|
||||
kStylisticAltOneOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '2'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltTwoOnSelector,
|
||||
kStylisticAltTwoOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '3'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltThreeOnSelector,
|
||||
kStylisticAltThreeOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '4'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltFourOnSelector,
|
||||
kStylisticAltFourOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '5'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltFiveOnSelector,
|
||||
kStylisticAltFiveOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '6'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltSixOnSelector,
|
||||
kStylisticAltSixOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '7'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltSevenOnSelector,
|
||||
kStylisticAltSevenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '8'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltEightOnSelector,
|
||||
kStylisticAltEightOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '0', '9'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltNineOnSelector,
|
||||
kStylisticAltNineOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '0'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltTenOnSelector,
|
||||
kStylisticAltTenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '1'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltElevenOnSelector,
|
||||
kStylisticAltElevenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '2'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltTwelveOnSelector,
|
||||
kStylisticAltTwelveOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '3'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltThirteenOnSelector,
|
||||
kStylisticAltThirteenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '4'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltFourteenOnSelector,
|
||||
kStylisticAltFourteenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '5'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltFifteenOnSelector,
|
||||
kStylisticAltFifteenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '6'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltSixteenOnSelector,
|
||||
kStylisticAltSixteenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '7'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltSeventeenOnSelector,
|
||||
kStylisticAltSeventeenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '8'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltEighteenOnSelector,
|
||||
kStylisticAltEighteenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '1', '9'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltNineteenOnSelector,
|
||||
kStylisticAltNineteenOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 's', '2', '0'):
|
||||
boolspec(value, kStylisticAlternativesType,
|
||||
kStylisticAltTwentyOnSelector,
|
||||
kStylisticAltTwentyOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('c', 'a', 'l', 't'):
|
||||
boolspec(value, kContextualAlternatesType,
|
||||
kContextualAlternatesOnSelector,
|
||||
kContextualAlternatesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('s', 'w', 's', 'h'):
|
||||
boolspec(value, kContextualAlternatesType,
|
||||
kSwashAlternatesOnSelector,
|
||||
kSwashAlternatesOffSelector,
|
||||
f);
|
||||
break;
|
||||
case mkTag('c', 's', 'w', 'h'):
|
||||
boolspec(value, kContextualAlternatesType,
|
||||
kContextualSwashAlternatesOnSelector,
|
||||
kContextualSwashAlternatesOffSelector,
|
||||
f);
|
||||
break;
|
||||
|
||||
// TODO will the following handle all cases properly, or are elses going to be needed?
|
||||
case mkTag('s', 'm', 'c', 'p'):
|
||||
if (value != 0) {
|
||||
// include this for compatibility (some fonts that come with OS X still use this!)
|
||||
// TODO make it boolean?
|
||||
f(kLetterCaseType, kSmallCapsSelector);
|
||||
// this is the current one
|
||||
f(kLowerCaseType, kLowerCaseSmallCapsSelector);
|
||||
}
|
||||
break;
|
||||
case mkTag('p', 'c', 'a', 'p'):
|
||||
if (value != 0)
|
||||
f(kLowerCaseType, kLowerCasePetiteCapsSelector);
|
||||
break;
|
||||
|
||||
// TODO will the following handle all cases properly, or are elses going to be needed?
|
||||
case mkTag('c', '2', 's', 'c'):
|
||||
if (value != 0)
|
||||
f(kUpperCaseType, kUpperCaseSmallCapsSelector);
|
||||
break;
|
||||
case mkTag('c', '2', 'p', 'c'):
|
||||
if (value != 0)
|
||||
f(kUpperCaseType, kUpperCasePetiteCapsSelector);
|
||||
break;
|
||||
}
|
||||
// TODO handle this properly
|
||||
// (it used to return 0 when this still returned the number of selectors produced but IDK what properly is anymore)
|
||||
}
|
||||
89
dep/libui/darwin/alloc.m
Normal file
89
dep/libui/darwin/alloc.m
Normal file
@@ -0,0 +1,89 @@
|
||||
// 4 december 2014
|
||||
#import <stdlib.h>
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
static NSMutableArray *allocations;
|
||||
NSMutableArray *uiprivDelegates;
|
||||
|
||||
void uiprivInitAlloc(void)
|
||||
{
|
||||
allocations = [NSMutableArray new];
|
||||
uiprivDelegates = [NSMutableArray new];
|
||||
}
|
||||
|
||||
#define UINT8(p) ((uint8_t *) (p))
|
||||
#define PVOID(p) ((void *) (p))
|
||||
#define EXTRA (sizeof (size_t) + sizeof (const char **))
|
||||
#define DATA(p) PVOID(UINT8(p) + EXTRA)
|
||||
#define BASE(p) PVOID(UINT8(p) - EXTRA)
|
||||
#define SIZE(p) ((size_t *) (p))
|
||||
#define CCHAR(p) ((const char **) (p))
|
||||
#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))
|
||||
|
||||
void uiprivUninitAlloc(void)
|
||||
{
|
||||
NSMutableString *str;
|
||||
NSValue *v;
|
||||
|
||||
[uiprivDelegates release];
|
||||
if ([allocations count] == 0) {
|
||||
[allocations release];
|
||||
return;
|
||||
}
|
||||
str = [NSMutableString new];
|
||||
for (v in allocations) {
|
||||
void *ptr;
|
||||
|
||||
ptr = [v pointerValue];
|
||||
[str appendString:[NSString stringWithFormat:@"%p %s\n", ptr, *TYPE(ptr)]];
|
||||
}
|
||||
uiprivUserBug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", [str UTF8String]);
|
||||
[str release];
|
||||
}
|
||||
|
||||
void *uiprivAlloc(size_t size, const char *type)
|
||||
{
|
||||
void *out;
|
||||
|
||||
out = malloc(EXTRA + size);
|
||||
if (out == NULL) {
|
||||
fprintf(stderr, "memory exhausted in uiAlloc()\n");
|
||||
abort();
|
||||
}
|
||||
memset(DATA(out), 0, size);
|
||||
*SIZE(out) = size;
|
||||
*TYPE(out) = type;
|
||||
[allocations addObject:[NSValue valueWithPointer:out]];
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void *uiprivRealloc(void *p, size_t new, const char *type)
|
||||
{
|
||||
void *out;
|
||||
size_t *s;
|
||||
|
||||
if (p == NULL)
|
||||
return uiprivAlloc(new, type);
|
||||
p = BASE(p);
|
||||
out = realloc(p, EXTRA + new);
|
||||
if (out == NULL) {
|
||||
fprintf(stderr, "memory exhausted in uiprivRealloc()\n");
|
||||
abort();
|
||||
}
|
||||
s = SIZE(out);
|
||||
if (new > *s)
|
||||
memset(((uint8_t *) DATA(out)) + *s, 0, new - *s);
|
||||
*s = new;
|
||||
[allocations removeObject:[NSValue valueWithPointer:p]];
|
||||
[allocations addObject:[NSValue valueWithPointer:out]];
|
||||
return DATA(out);
|
||||
}
|
||||
|
||||
void uiprivFree(void *p)
|
||||
{
|
||||
if (p == NULL)
|
||||
uiprivImplBug("attempt to uiprivFree(NULL)");
|
||||
p = BASE(p);
|
||||
free(p);
|
||||
[allocations removeObject:[NSValue valueWithPointer:p]];
|
||||
}
|
||||
474
dep/libui/darwin/area.m
Normal file
474
dep/libui/darwin/area.m
Normal file
@@ -0,0 +1,474 @@
|
||||
// 9 september 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// 10.8 fixups
|
||||
#define NSEventModifierFlags NSUInteger
|
||||
|
||||
@interface areaView : NSView {
|
||||
uiArea *libui_a;
|
||||
NSTrackingArea *libui_ta;
|
||||
NSSize libui_ss;
|
||||
BOOL libui_enabled;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r area:(uiArea *)a;
|
||||
- (uiModifiers)parseModifiers:(NSEvent *)e;
|
||||
- (void)doMouseEvent:(NSEvent *)e;
|
||||
- (int)sendKeyEvent:(uiAreaKeyEvent *)ke;
|
||||
- (int)doKeyDownUp:(NSEvent *)e up:(int)up;
|
||||
- (int)doKeyDown:(NSEvent *)e;
|
||||
- (int)doKeyUp:(NSEvent *)e;
|
||||
- (int)doFlagsChanged:(NSEvent *)e;
|
||||
- (void)setupNewTrackingArea;
|
||||
- (void)setScrollingSize:(NSSize)s;
|
||||
- (BOOL)isEnabled;
|
||||
- (void)setEnabled:(BOOL)e;
|
||||
@end
|
||||
|
||||
struct uiArea {
|
||||
uiDarwinControl c;
|
||||
NSView *view; // either sv or area depending on whether it is scrolling
|
||||
NSScrollView *sv;
|
||||
areaView *area;
|
||||
uiprivScrollViewData *d;
|
||||
uiAreaHandler *ah;
|
||||
BOOL scrolling;
|
||||
NSEvent *dragevent;
|
||||
};
|
||||
|
||||
@implementation areaView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r area:(uiArea *)a
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->libui_a = a;
|
||||
[self setupNewTrackingArea];
|
||||
self->libui_ss = r.size;
|
||||
self->libui_enabled = YES;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)r
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
CGContextRef c;
|
||||
uiAreaDrawParams dp;
|
||||
|
||||
c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
|
||||
// see draw.m under text for why we need the height
|
||||
dp.Context = uiprivDrawNewContext(c, [self bounds].size.height);
|
||||
|
||||
dp.AreaWidth = 0;
|
||||
dp.AreaHeight = 0;
|
||||
if (!a->scrolling) {
|
||||
dp.AreaWidth = [self frame].size.width;
|
||||
dp.AreaHeight = [self frame].size.height;
|
||||
}
|
||||
|
||||
dp.ClipX = r.origin.x;
|
||||
dp.ClipY = r.origin.y;
|
||||
dp.ClipWidth = r.size.width;
|
||||
dp.ClipHeight = r.size.height;
|
||||
|
||||
// no need to save or restore the graphics state to reset transformations; Cocoa creates a brand-new context each time
|
||||
(*(a->ah->Draw))(a->ah, a, &dp);
|
||||
|
||||
uiprivDrawFreeContext(dp.Context);
|
||||
}
|
||||
|
||||
- (BOOL)isFlipped
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstResponder
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (uiModifiers)parseModifiers:(NSEvent *)e
|
||||
{
|
||||
NSEventModifierFlags mods;
|
||||
uiModifiers m;
|
||||
|
||||
m = 0;
|
||||
mods = [e modifierFlags];
|
||||
if ((mods & NSControlKeyMask) != 0)
|
||||
m |= uiModifierCtrl;
|
||||
if ((mods & NSAlternateKeyMask) != 0)
|
||||
m |= uiModifierAlt;
|
||||
if ((mods & NSShiftKeyMask) != 0)
|
||||
m |= uiModifierShift;
|
||||
if ((mods & NSCommandKeyMask) != 0)
|
||||
m |= uiModifierSuper;
|
||||
return m;
|
||||
}
|
||||
|
||||
- (void)setupNewTrackingArea
|
||||
{
|
||||
self->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds]
|
||||
options:(NSTrackingMouseEnteredAndExited |
|
||||
NSTrackingMouseMoved |
|
||||
NSTrackingActiveAlways |
|
||||
NSTrackingInVisibleRect |
|
||||
NSTrackingEnabledDuringMouseDrag)
|
||||
owner:self
|
||||
userInfo:nil];
|
||||
[self addTrackingArea:self->libui_ta];
|
||||
}
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
[self removeTrackingArea:self->libui_ta];
|
||||
[self->libui_ta release];
|
||||
[self setupNewTrackingArea];
|
||||
}
|
||||
|
||||
// capture on drag is done automatically on OS X
|
||||
- (void)doMouseEvent:(NSEvent *)e
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
uiAreaMouseEvent me;
|
||||
NSPoint point;
|
||||
int buttonNumber;
|
||||
NSUInteger pmb;
|
||||
unsigned int i, max;
|
||||
|
||||
// this will convert point to drawing space
|
||||
// thanks swillits in irc.freenode.net/#macdev
|
||||
point = [self convertPoint:[e locationInWindow] fromView:nil];
|
||||
me.X = point.x;
|
||||
me.Y = point.y;
|
||||
|
||||
me.AreaWidth = 0;
|
||||
me.AreaHeight = 0;
|
||||
if (!a->scrolling) {
|
||||
me.AreaWidth = [self frame].size.width;
|
||||
me.AreaHeight = [self frame].size.height;
|
||||
}
|
||||
|
||||
buttonNumber = [e buttonNumber] + 1;
|
||||
// swap button numbers 2 and 3 (right and middle)
|
||||
if (buttonNumber == 2)
|
||||
buttonNumber = 3;
|
||||
else if (buttonNumber == 3)
|
||||
buttonNumber = 2;
|
||||
|
||||
me.Down = 0;
|
||||
me.Up = 0;
|
||||
me.Count = 0;
|
||||
switch ([e type]) {
|
||||
case NSLeftMouseDown:
|
||||
case NSRightMouseDown:
|
||||
case NSOtherMouseDown:
|
||||
me.Down = buttonNumber;
|
||||
me.Count = [e clickCount];
|
||||
break;
|
||||
case NSLeftMouseUp:
|
||||
case NSRightMouseUp:
|
||||
case NSOtherMouseUp:
|
||||
me.Up = buttonNumber;
|
||||
break;
|
||||
case NSLeftMouseDragged:
|
||||
case NSRightMouseDragged:
|
||||
case NSOtherMouseDragged:
|
||||
// we include the button that triggered the dragged event in the Held fields
|
||||
buttonNumber = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
me.Modifiers = [self parseModifiers:e];
|
||||
|
||||
pmb = [NSEvent pressedMouseButtons];
|
||||
me.Held1To64 = 0;
|
||||
if (buttonNumber != 1 && (pmb & 1) != 0)
|
||||
me.Held1To64 |= 1;
|
||||
if (buttonNumber != 2 && (pmb & 4) != 0)
|
||||
me.Held1To64 |= 2;
|
||||
if (buttonNumber != 3 && (pmb & 2) != 0)
|
||||
me.Held1To64 |= 4;
|
||||
// buttons 4..32
|
||||
// https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/tdef/CGMouseButton says Quartz only supports up to 32 buttons
|
||||
max = 32;
|
||||
for (i = 4; i <= max; i++) {
|
||||
uint64_t j;
|
||||
|
||||
if (buttonNumber == i)
|
||||
continue;
|
||||
j = 1 << (i - 1);
|
||||
if ((pmb & j) != 0)
|
||||
me.Held1To64 |= j;
|
||||
}
|
||||
|
||||
if (self->libui_enabled) {
|
||||
// and allow dragging here
|
||||
a->dragevent = e;
|
||||
(*(a->ah->MouseEvent))(a->ah, a, &me);
|
||||
a->dragevent = nil;
|
||||
}
|
||||
}
|
||||
|
||||
#define mouseEvent(name) \
|
||||
- (void)name:(NSEvent *)e \
|
||||
{ \
|
||||
[self doMouseEvent:e]; \
|
||||
}
|
||||
mouseEvent(mouseMoved)
|
||||
mouseEvent(mouseDragged)
|
||||
mouseEvent(rightMouseDragged)
|
||||
mouseEvent(otherMouseDragged)
|
||||
mouseEvent(mouseDown)
|
||||
mouseEvent(rightMouseDown)
|
||||
mouseEvent(otherMouseDown)
|
||||
mouseEvent(mouseUp)
|
||||
mouseEvent(rightMouseUp)
|
||||
mouseEvent(otherMouseUp)
|
||||
|
||||
- (void)mouseEntered:(NSEvent *)e
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
if (self->libui_enabled)
|
||||
(*(a->ah->MouseCrossed))(a->ah, a, 0);
|
||||
}
|
||||
|
||||
- (void)mouseExited:(NSEvent *)e
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
if (self->libui_enabled)
|
||||
(*(a->ah->MouseCrossed))(a->ah, a, 1);
|
||||
}
|
||||
|
||||
// note: there is no equivalent to WM_CAPTURECHANGED on Mac OS X; there literally is no way to break a grab like that
|
||||
// even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons
|
||||
// therefore, no DragBroken()
|
||||
|
||||
- (int)sendKeyEvent:(uiAreaKeyEvent *)ke
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
return (*(a->ah->KeyEvent))(a->ah, a, ke);
|
||||
}
|
||||
|
||||
- (int)doKeyDownUp:(NSEvent *)e up:(int)up
|
||||
{
|
||||
uiAreaKeyEvent ke;
|
||||
|
||||
ke.Key = 0;
|
||||
ke.ExtKey = 0;
|
||||
ke.Modifier = 0;
|
||||
|
||||
ke.Modifiers = [self parseModifiers:e];
|
||||
|
||||
ke.Up = up;
|
||||
|
||||
if (!uiprivFromKeycode([e keyCode], &ke))
|
||||
return 0;
|
||||
return [self sendKeyEvent:&ke];
|
||||
}
|
||||
|
||||
- (int)doKeyDown:(NSEvent *)e
|
||||
{
|
||||
return [self doKeyDownUp:e up:0];
|
||||
}
|
||||
|
||||
- (int)doKeyUp:(NSEvent *)e
|
||||
{
|
||||
return [self doKeyDownUp:e up:1];
|
||||
}
|
||||
|
||||
- (int)doFlagsChanged:(NSEvent *)e
|
||||
{
|
||||
uiAreaKeyEvent ke;
|
||||
uiModifiers whichmod;
|
||||
|
||||
ke.Key = 0;
|
||||
ke.ExtKey = 0;
|
||||
|
||||
// Mac OS X sends this event on both key up and key down.
|
||||
// Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state
|
||||
if (!uiprivKeycodeModifier([e keyCode], &whichmod))
|
||||
return 0;
|
||||
ke.Modifier = whichmod;
|
||||
ke.Modifiers = [self parseModifiers:e];
|
||||
ke.Up = (ke.Modifiers & ke.Modifier) == 0;
|
||||
// and then drop the current modifier from Modifiers
|
||||
ke.Modifiers &= ~ke.Modifier;
|
||||
return [self sendKeyEvent:&ke];
|
||||
}
|
||||
|
||||
- (void)setFrameSize:(NSSize)size
|
||||
{
|
||||
uiArea *a = self->libui_a;
|
||||
|
||||
[super setFrameSize:size];
|
||||
if (!a->scrolling)
|
||||
// we must redraw everything on resize because Windows requires it
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (void)setScrollingSize:(NSSize)s
|
||||
{
|
||||
self->libui_ss = s;
|
||||
[self setFrameSize:s];
|
||||
}
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
if (!self->libui_a->scrolling)
|
||||
return [super intrinsicContentSize];
|
||||
return self->libui_ss;
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
return [self isEnabled];
|
||||
}
|
||||
|
||||
- (BOOL)isEnabled
|
||||
{
|
||||
return self->libui_enabled;
|
||||
}
|
||||
|
||||
- (void)setEnabled:(BOOL)e
|
||||
{
|
||||
self->libui_enabled = e;
|
||||
if (!self->libui_enabled && [self window] != nil)
|
||||
if ([[self window] firstResponder] == self)
|
||||
[[self window] makeFirstResponder:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiArea, view)
|
||||
|
||||
static void uiAreaDestroy(uiControl *c)
|
||||
{
|
||||
uiArea *a = uiArea(c);
|
||||
|
||||
if (a->scrolling)
|
||||
uiprivScrollViewFreeData(a->sv, a->d);
|
||||
[a->area release];
|
||||
if (a->scrolling)
|
||||
[a->sv release];
|
||||
uiFreeControl(uiControl(a));
|
||||
}
|
||||
|
||||
// called by subclasses of -[NSApplication sendEvent:]
|
||||
// by default, NSApplication eats some key events
|
||||
// this prevents that from happening with uiArea
|
||||
// see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html
|
||||
int uiprivSendAreaEvents(NSEvent *e)
|
||||
{
|
||||
NSEventType type;
|
||||
id focused;
|
||||
areaView *view;
|
||||
|
||||
type = [e type];
|
||||
if (type != NSKeyDown && type != NSKeyUp && type != NSFlagsChanged)
|
||||
return 0;
|
||||
focused = [[e window] firstResponder];
|
||||
if (focused == nil)
|
||||
return 0;
|
||||
if (![focused isKindOfClass:[areaView class]])
|
||||
return 0;
|
||||
view = (areaView *) focused;
|
||||
switch (type) {
|
||||
case NSKeyDown:
|
||||
return [view doKeyDown:e];
|
||||
case NSKeyUp:
|
||||
return [view doKeyUp:e];
|
||||
case NSFlagsChanged:
|
||||
return [view doFlagsChanged:e];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void uiAreaSetSize(uiArea *a, int width, int height)
|
||||
{
|
||||
if (!a->scrolling)
|
||||
uiprivUserBug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
|
||||
[a->area setScrollingSize:NSMakeSize(width, height)];
|
||||
}
|
||||
|
||||
void uiAreaQueueRedrawAll(uiArea *a)
|
||||
{
|
||||
[a->area setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
|
||||
{
|
||||
if (!a->scrolling)
|
||||
uiprivUserBug("You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)", a);
|
||||
[a->area scrollRectToVisible:NSMakeRect(x, y, width, height)];
|
||||
// don't worry about the return value; it just says whether scrolling was needed
|
||||
}
|
||||
|
||||
void uiAreaBeginUserWindowMove(uiArea *a)
|
||||
{
|
||||
uiprivNSWindow *w;
|
||||
|
||||
w = (uiprivNSWindow *) [a->area window];
|
||||
if (w == nil)
|
||||
return; // TODO
|
||||
if (a->dragevent == nil)
|
||||
return; // TODO
|
||||
[w uiprivDoMove:a->dragevent];
|
||||
}
|
||||
|
||||
void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)
|
||||
{
|
||||
uiprivNSWindow *w;
|
||||
|
||||
w = (uiprivNSWindow *) [a->area window];
|
||||
if (w == nil)
|
||||
return; // TODO
|
||||
if (a->dragevent == nil)
|
||||
return; // TODO
|
||||
[w uiprivDoResize:a->dragevent on:edge];
|
||||
}
|
||||
|
||||
uiArea *uiNewArea(uiAreaHandler *ah)
|
||||
{
|
||||
uiArea *a;
|
||||
|
||||
uiDarwinNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = NO;
|
||||
|
||||
a->area = [[areaView alloc] initWithFrame:NSZeroRect area:a];
|
||||
|
||||
a->view = a->area;
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)
|
||||
{
|
||||
uiArea *a;
|
||||
uiprivScrollViewCreateParams p;
|
||||
|
||||
uiDarwinNewControl(uiArea, a);
|
||||
|
||||
a->ah = ah;
|
||||
a->scrolling = YES;
|
||||
|
||||
a->area = [[areaView alloc] initWithFrame:NSMakeRect(0, 0, width, height)
|
||||
area:a];
|
||||
|
||||
memset(&p, 0, sizeof (uiprivScrollViewCreateParams));
|
||||
p.DocumentView = a->area;
|
||||
p.BackgroundColor = [NSColor controlColor];
|
||||
p.DrawsBackground = 1;
|
||||
p.Bordered = NO;
|
||||
p.HScroll = YES;
|
||||
p.VScroll = YES;
|
||||
a->sv = uiprivMkScrollView(&p, &(a->d));
|
||||
|
||||
a->view = a->sv;
|
||||
|
||||
return a;
|
||||
}
|
||||
159
dep/libui/darwin/areaevents.m
Normal file
159
dep/libui/darwin/areaevents.m
Normal file
@@ -0,0 +1,159 @@
|
||||
// 30 march 2014
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
/*
|
||||
Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.
|
||||
|
||||
The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
|
||||
*/
|
||||
|
||||
// use uintptr_t to be safe
|
||||
static const struct {
|
||||
uintptr_t keycode;
|
||||
char equiv;
|
||||
} keycodeKeys[] = {
|
||||
{ 0x00, 'a' },
|
||||
{ 0x01, 's' },
|
||||
{ 0x02, 'd' },
|
||||
{ 0x03, 'f' },
|
||||
{ 0x04, 'h' },
|
||||
{ 0x05, 'g' },
|
||||
{ 0x06, 'z' },
|
||||
{ 0x07, 'x' },
|
||||
{ 0x08, 'c' },
|
||||
{ 0x09, 'v' },
|
||||
{ 0x0B, 'b' },
|
||||
{ 0x0C, 'q' },
|
||||
{ 0x0D, 'w' },
|
||||
{ 0x0E, 'e' },
|
||||
{ 0x0F, 'r' },
|
||||
{ 0x10, 'y' },
|
||||
{ 0x11, 't' },
|
||||
{ 0x12, '1' },
|
||||
{ 0x13, '2' },
|
||||
{ 0x14, '3' },
|
||||
{ 0x15, '4' },
|
||||
{ 0x16, '6' },
|
||||
{ 0x17, '5' },
|
||||
{ 0x18, '=' },
|
||||
{ 0x19, '9' },
|
||||
{ 0x1A, '7' },
|
||||
{ 0x1B, '-' },
|
||||
{ 0x1C, '8' },
|
||||
{ 0x1D, '0' },
|
||||
{ 0x1E, ']' },
|
||||
{ 0x1F, 'o' },
|
||||
{ 0x20, 'u' },
|
||||
{ 0x21, '[' },
|
||||
{ 0x22, 'i' },
|
||||
{ 0x23, 'p' },
|
||||
{ 0x25, 'l' },
|
||||
{ 0x26, 'j' },
|
||||
{ 0x27, '\'' },
|
||||
{ 0x28, 'k' },
|
||||
{ 0x29, ';' },
|
||||
{ 0x2A, '\\' },
|
||||
{ 0x2B, ',' },
|
||||
{ 0x2C, '/' },
|
||||
{ 0x2D, 'n' },
|
||||
{ 0x2E, 'm' },
|
||||
{ 0x2F, '.' },
|
||||
{ 0x32, '`' },
|
||||
{ 0x24, '\n' },
|
||||
{ 0x30, '\t' },
|
||||
{ 0x31, ' ' },
|
||||
{ 0x33, '\b' },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t keycode;
|
||||
uiExtKey equiv;
|
||||
} keycodeExtKeys[] = {
|
||||
{ 0x41, uiExtKeyNDot },
|
||||
{ 0x43, uiExtKeyNMultiply },
|
||||
{ 0x45, uiExtKeyNAdd },
|
||||
{ 0x4B, uiExtKeyNDivide },
|
||||
{ 0x4C, uiExtKeyNEnter },
|
||||
{ 0x4E, uiExtKeyNSubtract },
|
||||
{ 0x52, uiExtKeyN0 },
|
||||
{ 0x53, uiExtKeyN1 },
|
||||
{ 0x54, uiExtKeyN2 },
|
||||
{ 0x55, uiExtKeyN3 },
|
||||
{ 0x56, uiExtKeyN4 },
|
||||
{ 0x57, uiExtKeyN5 },
|
||||
{ 0x58, uiExtKeyN6 },
|
||||
{ 0x59, uiExtKeyN7 },
|
||||
{ 0x5B, uiExtKeyN8 },
|
||||
{ 0x5C, uiExtKeyN9 },
|
||||
{ 0x35, uiExtKeyEscape },
|
||||
{ 0x60, uiExtKeyF5 },
|
||||
{ 0x61, uiExtKeyF6 },
|
||||
{ 0x62, uiExtKeyF7 },
|
||||
{ 0x63, uiExtKeyF3 },
|
||||
{ 0x64, uiExtKeyF8 },
|
||||
{ 0x65, uiExtKeyF9 },
|
||||
{ 0x67, uiExtKeyF11 },
|
||||
{ 0x6D, uiExtKeyF10 },
|
||||
{ 0x6F, uiExtKeyF12 },
|
||||
{ 0x72, uiExtKeyInsert }, // listed as the Help key but it's in the same position on an Apple keyboard as the Insert key on a Windows keyboard; thanks to SeanieB from irc.badnik.net and Psy in irc.freenode.net/#macdev for confirming they have the same code
|
||||
{ 0x73, uiExtKeyHome },
|
||||
{ 0x74, uiExtKeyPageUp },
|
||||
{ 0x75, uiExtKeyDelete },
|
||||
{ 0x76, uiExtKeyF4 },
|
||||
{ 0x77, uiExtKeyEnd },
|
||||
{ 0x78, uiExtKeyF2 },
|
||||
{ 0x79, uiExtKeyPageDown },
|
||||
{ 0x7A, uiExtKeyF1 },
|
||||
{ 0x7B, uiExtKeyLeft },
|
||||
{ 0x7C, uiExtKeyRight },
|
||||
{ 0x7D, uiExtKeyDown },
|
||||
{ 0x7E, uiExtKeyUp },
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
static const struct {
|
||||
uintptr_t keycode;
|
||||
uiModifiers equiv;
|
||||
} keycodeModifiers[] = {
|
||||
{ 0x37, uiModifierSuper }, // left command
|
||||
{ 0x38, uiModifierShift }, // left shift
|
||||
{ 0x3A, uiModifierAlt }, // left option
|
||||
{ 0x3B, uiModifierCtrl }, // left control
|
||||
{ 0x3C, uiModifierShift }, // right shift
|
||||
{ 0x3D, uiModifierAlt }, // right alt
|
||||
{ 0x3E, uiModifierCtrl }, // right control
|
||||
// the following is not in Events.h for some reason
|
||||
// thanks to Nicole and jedivulcan from irc.badnik.net
|
||||
{ 0x36, uiModifierSuper }, // right command
|
||||
{ 0xFFFF, 0 },
|
||||
};
|
||||
|
||||
BOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; keycodeKeys[i].keycode != 0xFFFF; i++)
|
||||
if (keycodeKeys[i].keycode == keycode) {
|
||||
ke->Key = keycodeKeys[i].equiv;
|
||||
return YES;
|
||||
}
|
||||
for (i = 0; keycodeExtKeys[i].keycode != 0xFFFF; i++)
|
||||
if (keycodeExtKeys[i].keycode == keycode) {
|
||||
ke->ExtKey = keycodeExtKeys[i].equiv;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; keycodeModifiers[i].keycode != 0xFFFF; i++)
|
||||
if (keycodeModifiers[i].keycode == keycode) {
|
||||
*mod = keycodeModifiers[i].equiv;
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
91
dep/libui/darwin/attrstr.h
Normal file
91
dep/libui/darwin/attrstr.h
Normal file
@@ -0,0 +1,91 @@
|
||||
// 4 march 2018
|
||||
#import "../common/attrstr.h"
|
||||
|
||||
// opentype.m
|
||||
extern CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf);
|
||||
|
||||
// aat.m
|
||||
typedef void (^uiprivAATBlock)(uint16_t type, uint16_t selector);
|
||||
extern void uiprivOpenTypeToAAT(char a, char b, char c, char d, uint32_t value, uiprivAATBlock f);
|
||||
|
||||
// fontmatch.m
|
||||
@interface uiprivFontStyleData : NSObject {
|
||||
CTFontRef font;
|
||||
CTFontDescriptorRef desc;
|
||||
CFDictionaryRef traits;
|
||||
CTFontSymbolicTraits symbolic;
|
||||
double weight;
|
||||
double width;
|
||||
BOOL didStyleName;
|
||||
CFStringRef styleName;
|
||||
BOOL didVariation;
|
||||
CFDictionaryRef variation;
|
||||
BOOL hasRegistrationScope;
|
||||
CTFontManagerScope registrationScope;
|
||||
BOOL didPostScriptName;
|
||||
CFStringRef postScriptName;
|
||||
CTFontFormat fontFormat;
|
||||
BOOL didPreferredSubFamilyName;
|
||||
CFStringRef preferredSubFamilyName;
|
||||
BOOL didSubFamilyName;
|
||||
CFStringRef subFamilyName;
|
||||
BOOL didFullName;
|
||||
CFStringRef fullName;
|
||||
BOOL didPreferredFamilyName;
|
||||
CFStringRef preferredFamilyName;
|
||||
BOOL didFamilyName;
|
||||
CFStringRef familyName;
|
||||
BOOL didVariationAxes;
|
||||
CFArrayRef variationAxes;
|
||||
}
|
||||
- (id)initWithFont:(CTFontRef)f;
|
||||
- (id)initWithDescriptor:(CTFontDescriptorRef)d;
|
||||
- (BOOL)prepare;
|
||||
- (void)ensureFont;
|
||||
- (CTFontSymbolicTraits)symbolicTraits;
|
||||
- (double)weight;
|
||||
- (double)width;
|
||||
- (CFStringRef)styleName;
|
||||
- (CFDictionaryRef)variation;
|
||||
- (BOOL)hasRegistrationScope;
|
||||
- (CTFontManagerScope)registrationScope;
|
||||
- (CFStringRef)postScriptName;
|
||||
- (CFDataRef)table:(CTFontTableTag)tag;
|
||||
- (CTFontFormat)fontFormat;
|
||||
- (CFStringRef)fontName:(CFStringRef)key;
|
||||
- (CFStringRef)preferredSubFamilyName;
|
||||
- (CFStringRef)subFamilyName;
|
||||
- (CFStringRef)fullName;
|
||||
- (CFStringRef)preferredFamilyName;
|
||||
- (CFStringRef)familyName;
|
||||
- (CFArrayRef)variationAxes;
|
||||
@end
|
||||
extern CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd);
|
||||
extern CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf);
|
||||
extern void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc);
|
||||
|
||||
// fonttraits.m
|
||||
extern void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out);
|
||||
|
||||
// fontvariation.m
|
||||
extern NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable);
|
||||
extern void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out);
|
||||
|
||||
// attrstr.m
|
||||
extern void uiprivInitUnderlineColors(void);
|
||||
extern void uiprivUninitUnderlineColors(void);
|
||||
extern CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams);
|
||||
|
||||
// drawtext.m
|
||||
// TODO figure out where this type should *really* go in all the headers...
|
||||
@interface uiprivDrawTextBackgroundParams : NSObject {
|
||||
size_t start;
|
||||
size_t end;
|
||||
double r;
|
||||
double g;
|
||||
double b;
|
||||
double a;
|
||||
}
|
||||
- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha;
|
||||
- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8;
|
||||
@end
|
||||
505
dep/libui/darwin/attrstr.m
Normal file
505
dep/libui/darwin/attrstr.m
Normal file
@@ -0,0 +1,505 @@
|
||||
// 12 february 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// this is what AppKit does internally
|
||||
// WebKit does this too; see https://github.com/adobe/webkit/blob/master/Source/WebCore/platform/graphics/mac/GraphicsContextMac.mm
|
||||
static NSColor *spellingColor = nil;
|
||||
static NSColor *grammarColor = nil;
|
||||
static NSColor *auxiliaryColor = nil;
|
||||
|
||||
static NSColor *tryColorNamed(NSString *name)
|
||||
{
|
||||
NSImage *img;
|
||||
|
||||
img = [NSImage imageNamed:name];
|
||||
if (img == nil)
|
||||
return nil;
|
||||
return [NSColor colorWithPatternImage:img];
|
||||
}
|
||||
|
||||
void uiprivInitUnderlineColors(void)
|
||||
{
|
||||
spellingColor = tryColorNamed(@"NSSpellingDot");
|
||||
if (spellingColor == nil) {
|
||||
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
|
||||
spellingColor = tryColorNamed(@"SpellingDot");
|
||||
if (spellingColor == nil)
|
||||
spellingColor = [NSColor redColor];
|
||||
}
|
||||
[spellingColor retain]; // override autoreleasing
|
||||
|
||||
grammarColor = tryColorNamed(@"NSGrammarDot");
|
||||
if (grammarColor == nil) {
|
||||
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
|
||||
grammarColor = tryColorNamed(@"GrammarDot");
|
||||
if (grammarColor == nil)
|
||||
grammarColor = [NSColor greenColor];
|
||||
}
|
||||
[grammarColor retain]; // override autoreleasing
|
||||
|
||||
auxiliaryColor = tryColorNamed(@"NSCorrectionDot");
|
||||
if (auxiliaryColor == nil) {
|
||||
// WebKit says this is needed for "older systems"; not sure how old, but 10.11 AppKit doesn't look for this
|
||||
auxiliaryColor = tryColorNamed(@"CorrectionDot");
|
||||
if (auxiliaryColor == nil)
|
||||
auxiliaryColor = [NSColor blueColor];
|
||||
}
|
||||
[auxiliaryColor retain]; // override autoreleasing
|
||||
}
|
||||
|
||||
void uiprivUninitUnderlineColors(void)
|
||||
{
|
||||
[auxiliaryColor release];
|
||||
auxiliaryColor = nil;
|
||||
[grammarColor release];
|
||||
grammarColor = nil;
|
||||
[spellingColor release];
|
||||
spellingColor = nil;
|
||||
}
|
||||
|
||||
// TODO opentype features are lost when using uiFontDescriptor, so a handful of fonts in the font panel ("Titling" variants of some fonts and possibly others but those are the examples I know about) cannot be represented by uiFontDescriptor; what *can* we do about this since this info is NOT part of the font on other platforms?
|
||||
// TODO see if we could use NSAttributedString?
|
||||
// TODO consider renaming this struct and the fep variable(s)
|
||||
// TODO restructure all this so the important details at the top are below with the combined font attributes type?
|
||||
// TODO in fact I should just write something to explain everything in this file...
|
||||
struct foreachParams {
|
||||
CFMutableAttributedStringRef mas;
|
||||
NSMutableArray *backgroundParams;
|
||||
};
|
||||
|
||||
// unlike the other systems, Core Text rolls family, size, weight, italic, width, AND opentype features into the "font" attribute
|
||||
// instead of incrementally adjusting CTFontRefs (which, judging from NSFontManager, seems finicky and UI-centric), we use a custom class to incrementally store attributes that go into a CTFontRef, and then convert everything to CTFonts en masse later
|
||||
// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/ChangingAttrStrings.html#//apple_ref/doc/uid/20000162-BBCBGCDG says we must have -hash and -isEqual: workign properly for this to work, so we must do that too, using a basic xor-based hash and leveraging Cocoa -hash implementations where useful and feasible (if not necessary)
|
||||
// TODO structure and rewrite this part
|
||||
// TODO re-find sources proving support of custom attributes
|
||||
// TODO what if this is NULL?
|
||||
static const CFStringRef combinedFontAttrName = CFSTR("libuiCombinedFontAttribute");
|
||||
|
||||
enum {
|
||||
cFamily,
|
||||
cSize,
|
||||
cWeight,
|
||||
cItalic,
|
||||
cStretch,
|
||||
cFeatures,
|
||||
nc,
|
||||
};
|
||||
|
||||
static const int toc[] = {
|
||||
[uiAttributeTypeFamily] = cFamily,
|
||||
[uiAttributeTypeSize] = cSize,
|
||||
[uiAttributeTypeWeight] = cWeight,
|
||||
[uiAttributeTypeItalic] = cItalic,
|
||||
[uiAttributeTypeStretch] = cStretch,
|
||||
[uiAttributeTypeFeatures] = cFeatures,
|
||||
};
|
||||
|
||||
static uiForEach featuresHash(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
|
||||
{
|
||||
NSUInteger *hash = (NSUInteger *) data;
|
||||
uint32_t tag;
|
||||
|
||||
tag = (((uint32_t) a) & 0xFF) << 24;
|
||||
tag |= (((uint32_t) b) & 0xFF) << 16;
|
||||
tag |= (((uint32_t) c) & 0xFF) << 8;
|
||||
tag |= ((uint32_t) d) & 0xFF;
|
||||
*hash ^= tag;
|
||||
*hash ^= value;
|
||||
return uiForEachContinue;
|
||||
}
|
||||
|
||||
@interface uiprivCombinedFontAttr : NSObject<NSCopying> {
|
||||
uiAttribute *attrs[nc];
|
||||
BOOL hasHash;
|
||||
NSUInteger hash;
|
||||
}
|
||||
- (void)addAttribute:(uiAttribute *)attr;
|
||||
- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont;
|
||||
@end
|
||||
|
||||
@implementation uiprivCombinedFontAttr
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
memset(self->attrs, 0, nc * sizeof (uiAttribute *));
|
||||
self->hasHash = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nc; i++)
|
||||
if (self->attrs[i] != NULL) {
|
||||
uiprivAttributeRelease(self->attrs[i]);
|
||||
self->attrs[i] = NULL;
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id)copyWithZone:(NSZone *)zone
|
||||
{
|
||||
uiprivCombinedFontAttr *ret;
|
||||
int i;
|
||||
|
||||
ret = [[uiprivCombinedFontAttr allocWithZone:zone] init];
|
||||
for (i = 0; i < nc; i++)
|
||||
if (self->attrs[i] != NULL)
|
||||
ret->attrs[i] = uiprivAttributeRetain(self->attrs[i]);
|
||||
ret->hasHash = self->hasHash;
|
||||
ret->hash = self->hash;
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (void)addAttribute:(uiAttribute *)attr
|
||||
{
|
||||
int index;
|
||||
|
||||
index = toc[uiAttributeGetType(attr)];
|
||||
if (self->attrs[index] != NULL)
|
||||
uiprivAttributeRelease(self->attrs[index]);
|
||||
self->attrs[index] = uiprivAttributeRetain(attr);
|
||||
self->hasHash = NO;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)bb
|
||||
{
|
||||
uiprivCombinedFontAttr *b = (uiprivCombinedFontAttr *) bb;
|
||||
int i;
|
||||
|
||||
if (b == nil)
|
||||
return NO;
|
||||
for (i = 0; i < nc; i++) {
|
||||
if (self->attrs[i] == NULL && b->attrs[i] == NULL)
|
||||
continue;
|
||||
if (self->attrs[i] == NULL || b->attrs[i] == NULL)
|
||||
return NO;
|
||||
if (!uiprivAttributeEqual(self->attrs[i], b->attrs[i]))
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSUInteger)hash
|
||||
{
|
||||
if (self->hasHash)
|
||||
return self->hash;
|
||||
@autoreleasepool {
|
||||
NSString *family;
|
||||
NSNumber *size;
|
||||
|
||||
self->hash = 0;
|
||||
if (self->attrs[cFamily] != NULL) {
|
||||
family = [NSString stringWithUTF8String:uiAttributeFamily(self->attrs[cFamily])];
|
||||
// TODO make sure this aligns with case-insensitive compares when those are done in common/attribute.c
|
||||
self->hash ^= [[family uppercaseString] hash];
|
||||
}
|
||||
if (self->attrs[cSize] != NULL) {
|
||||
size = [NSNumber numberWithDouble:uiAttributeSize(self->attrs[cSize])];
|
||||
self->hash ^= [size hash];
|
||||
}
|
||||
if (self->attrs[cWeight] != NULL)
|
||||
self->hash ^= (NSUInteger) uiAttributeWeight(self->attrs[cWeight]);
|
||||
if (self->attrs[cItalic] != NULL)
|
||||
self->hash ^= (NSUInteger) uiAttributeItalic(self->attrs[cItalic]);
|
||||
if (self->attrs[cStretch] != NULL)
|
||||
self->hash ^= (NSUInteger) uiAttributeStretch(self->attrs[cStretch]);
|
||||
if (self->attrs[cFeatures] != NULL)
|
||||
uiOpenTypeFeaturesForEach(uiAttributeFeatures(self->attrs[cFeatures]), featuresHash, &(self->hash));
|
||||
self->hasHash = YES;
|
||||
}
|
||||
return self->hash;
|
||||
}
|
||||
|
||||
- (CTFontRef)toCTFontWithDefaultFont:(uiFontDescriptor *)defaultFont
|
||||
{
|
||||
uiFontDescriptor uidesc;
|
||||
CTFontDescriptorRef desc;
|
||||
CTFontRef font;
|
||||
|
||||
uidesc = *defaultFont;
|
||||
if (self->attrs[cFamily] != NULL)
|
||||
// TODO const-correct uiFontDescriptor or change this function below
|
||||
uidesc.Family = (char *) uiAttributeFamily(self->attrs[cFamily]);
|
||||
if (self->attrs[cSize] != NULL)
|
||||
uidesc.Size = uiAttributeSize(self->attrs[cSize]);
|
||||
if (self->attrs[cWeight] != NULL)
|
||||
uidesc.Weight = uiAttributeWeight(self->attrs[cWeight]);
|
||||
if (self->attrs[cItalic] != NULL)
|
||||
uidesc.Italic = uiAttributeItalic(self->attrs[cItalic]);
|
||||
if (self->attrs[cStretch] != NULL)
|
||||
uidesc.Stretch = uiAttributeStretch(self->attrs[cStretch]);
|
||||
desc = uiprivFontDescriptorToCTFontDescriptor(&uidesc);
|
||||
if (self->attrs[cFeatures] != NULL)
|
||||
desc = uiprivCTFontDescriptorAppendFeatures(desc, uiAttributeFeatures(self->attrs[cFeatures]));
|
||||
font = CTFontCreateWithFontDescriptor(desc, uidesc.Size, NULL);
|
||||
CFRelease(desc); // TODO correct?
|
||||
return font;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void addFontAttributeToRange(struct foreachParams *p, size_t start, size_t end, uiAttribute *attr)
|
||||
{
|
||||
uiprivCombinedFontAttr *cfa;
|
||||
CFRange range;
|
||||
size_t diff;
|
||||
|
||||
while (start < end) {
|
||||
cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(p->mas, start, combinedFontAttrName, &range);
|
||||
if (cfa == nil)
|
||||
cfa = [uiprivCombinedFontAttr new];
|
||||
else
|
||||
cfa = [cfa copy];
|
||||
[cfa addAttribute:attr];
|
||||
// clamp range within [start, end)
|
||||
if (range.location < start) {
|
||||
diff = start - range.location;
|
||||
range.location = start;
|
||||
range.length -= diff;
|
||||
}
|
||||
if ((range.location + range.length) > end)
|
||||
range.length = end - range.location;
|
||||
CFAttributedStringSetAttribute(p->mas, range, combinedFontAttrName, cfa);
|
||||
[cfa release];
|
||||
start += range.length;
|
||||
}
|
||||
}
|
||||
|
||||
static CGColorRef mkcolor(double r, double g, double b, double a)
|
||||
{
|
||||
CGColorSpaceRef colorspace;
|
||||
CGColorRef color;
|
||||
CGFloat components[4];
|
||||
|
||||
// TODO we should probably just create this once and recycle it throughout program execution...
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
if (colorspace == NULL) {
|
||||
// TODO
|
||||
}
|
||||
components[0] = r;
|
||||
components[1] = g;
|
||||
components[2] = b;
|
||||
components[3] = a;
|
||||
color = CGColorCreate(colorspace, components);
|
||||
CFRelease(colorspace);
|
||||
return color;
|
||||
}
|
||||
|
||||
static void addBackgroundAttribute(struct foreachParams *p, size_t start, size_t end, double r, double g, double b, double a)
|
||||
{
|
||||
uiprivDrawTextBackgroundParams *dtb;
|
||||
|
||||
// TODO make sure this works properly with line paragraph spacings (after figuring out what that means, of course)
|
||||
if (uiprivFUTURE_kCTBackgroundColorAttributeName != NULL) {
|
||||
CGColorRef color;
|
||||
CFRange range;
|
||||
|
||||
color = mkcolor(r, g, b, a);
|
||||
range.location = start;
|
||||
range.length = end - start;
|
||||
CFAttributedStringSetAttribute(p->mas, range, *uiprivFUTURE_kCTBackgroundColorAttributeName, color);
|
||||
CFRelease(color);
|
||||
return;
|
||||
}
|
||||
|
||||
dtb = [[uiprivDrawTextBackgroundParams alloc] initWithStart:start end:end r:r g:g b:b a:a];
|
||||
[p->backgroundParams addObject:dtb];
|
||||
[dtb release];
|
||||
}
|
||||
|
||||
static uiForEach processAttribute(const uiAttributedString *s, const uiAttribute *attr, size_t start, size_t end, void *data)
|
||||
{
|
||||
struct foreachParams *p = (struct foreachParams *) data;
|
||||
CFRange range;
|
||||
CGColorRef color;
|
||||
int32_t us;
|
||||
CFNumberRef num;
|
||||
double r, g, b, a;
|
||||
uiUnderlineColor colorType;
|
||||
|
||||
start = uiprivAttributedStringUTF8ToUTF16(s, start);
|
||||
end = uiprivAttributedStringUTF8ToUTF16(s, end);
|
||||
range.location = start;
|
||||
range.length = end - start;
|
||||
switch (uiAttributeGetType(attr)) {
|
||||
case uiAttributeTypeFamily:
|
||||
case uiAttributeTypeSize:
|
||||
case uiAttributeTypeWeight:
|
||||
case uiAttributeTypeItalic:
|
||||
case uiAttributeTypeStretch:
|
||||
case uiAttributeTypeFeatures:
|
||||
addFontAttributeToRange(p, start, end, attr);
|
||||
break;
|
||||
case uiAttributeTypeColor:
|
||||
uiAttributeColor(attr, &r, &g, &b, &a);
|
||||
color = mkcolor(r, g, b, a);
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTForegroundColorAttributeName, color);
|
||||
CFRelease(color);
|
||||
break;
|
||||
case uiAttributeTypeBackground:
|
||||
uiAttributeColor(attr, &r, &g, &b, &a);
|
||||
addBackgroundAttribute(p, start, end, r, g, b, a);
|
||||
break;
|
||||
// TODO turn into a class, like we did with the font attributes, or even integrate *into* the font attributes
|
||||
case uiAttributeTypeUnderline:
|
||||
switch (uiAttributeUnderline(attr)) {
|
||||
case uiUnderlineNone:
|
||||
us = kCTUnderlineStyleNone;
|
||||
break;
|
||||
case uiUnderlineSingle:
|
||||
us = kCTUnderlineStyleSingle;
|
||||
break;
|
||||
case uiUnderlineDouble:
|
||||
us = kCTUnderlineStyleDouble;
|
||||
break;
|
||||
case uiUnderlineSuggestion:
|
||||
// TODO incorrect if a solid color
|
||||
us = kCTUnderlineStyleThick;
|
||||
break;
|
||||
}
|
||||
num = CFNumberCreate(NULL, kCFNumberSInt32Type, &us);
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineStyleAttributeName, num);
|
||||
CFRelease(num);
|
||||
break;
|
||||
case uiAttributeTypeUnderlineColor:
|
||||
uiAttributeUnderlineColor(attr, &colorType, &r, &g, &b, &a);
|
||||
switch (colorType) {
|
||||
case uiUnderlineColorCustom:
|
||||
color = mkcolor(r, g, b, a);
|
||||
break;
|
||||
case uiUnderlineColorSpelling:
|
||||
color = [spellingColor CGColor];
|
||||
break;
|
||||
case uiUnderlineColorGrammar:
|
||||
color = [grammarColor CGColor];
|
||||
break;
|
||||
case uiUnderlineColorAuxiliary:
|
||||
color = [auxiliaryColor CGColor];
|
||||
break;
|
||||
}
|
||||
CFAttributedStringSetAttribute(p->mas, range, kCTUnderlineColorAttributeName, color);
|
||||
if (colorType == uiUnderlineColorCustom)
|
||||
CFRelease(color);
|
||||
break;
|
||||
}
|
||||
return uiForEachContinue;
|
||||
}
|
||||
|
||||
static void applyFontAttributes(CFMutableAttributedStringRef mas, uiFontDescriptor *defaultFont)
|
||||
{
|
||||
uiprivCombinedFontAttr *cfa;
|
||||
CTFontRef font;
|
||||
CFRange range;
|
||||
CFIndex n;
|
||||
|
||||
n = CFAttributedStringGetLength(mas);
|
||||
|
||||
// first apply the default font to the entire string
|
||||
// TODO is this necessary given the #if 0'd code in uiprivAttributedStringToCFAttributedString()?
|
||||
cfa = [uiprivCombinedFontAttr new];
|
||||
font = [cfa toCTFontWithDefaultFont:defaultFont];
|
||||
[cfa release];
|
||||
range.location = 0;
|
||||
range.length = n;
|
||||
CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);
|
||||
CFRelease(font);
|
||||
|
||||
// now go through, replacing every uiprivCombinedFontAttr with the proper CTFontRef
|
||||
// we are best off treating series of identical fonts as single ranges ourselves for parity across platforms, even if OS X does something similar itself
|
||||
range.location = 0;
|
||||
while (range.location < n) {
|
||||
// TODO consider seeing if CFAttributedStringGetAttributeAndLongestEffectiveRange() can make things faster by reducing the number of potential iterations, either here or above
|
||||
cfa = (uiprivCombinedFontAttr *) CFAttributedStringGetAttribute(mas, range.location, combinedFontAttrName, &range);
|
||||
if (cfa != nil) {
|
||||
font = [cfa toCTFontWithDefaultFont:defaultFont];
|
||||
CFAttributedStringSetAttribute(mas, range, kCTFontAttributeName, font);
|
||||
CFRelease(font);
|
||||
}
|
||||
range.location += range.length;
|
||||
}
|
||||
|
||||
// and finally, get rid of all the uiprivCombinedFontAttrs as we won't need them anymore
|
||||
range.location = 0;
|
||||
range.length = 0;
|
||||
CFAttributedStringRemoveAttribute(mas, range, combinedFontAttrName);
|
||||
}
|
||||
|
||||
static const CTTextAlignment ctaligns[] = {
|
||||
[uiDrawTextAlignLeft] = kCTTextAlignmentLeft,
|
||||
[uiDrawTextAlignCenter] = kCTTextAlignmentCenter,
|
||||
[uiDrawTextAlignRight] = kCTTextAlignmentRight,
|
||||
};
|
||||
|
||||
static CTParagraphStyleRef mkParagraphStyle(uiDrawTextLayoutParams *p)
|
||||
{
|
||||
CTParagraphStyleRef ps;
|
||||
CTParagraphStyleSetting settings[16];
|
||||
size_t nSettings = 0;
|
||||
|
||||
settings[nSettings].spec = kCTParagraphStyleSpecifierAlignment;
|
||||
settings[nSettings].valueSize = sizeof (CTTextAlignment);
|
||||
settings[nSettings].value = ctaligns + p->Align;
|
||||
nSettings++;
|
||||
|
||||
ps = CTParagraphStyleCreate(settings, nSettings);
|
||||
if (ps == NULL) {
|
||||
// TODO
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
// TODO either rename this (on all platforms) to uiprivDrawTextLayoutParams... or rename this file or both or split the struct or something else...
|
||||
CFAttributedStringRef uiprivAttributedStringToCFAttributedString(uiDrawTextLayoutParams *p, NSArray **backgroundParams)
|
||||
{
|
||||
CFStringRef cfstr;
|
||||
CFMutableDictionaryRef defaultAttrs;
|
||||
CTParagraphStyleRef ps;
|
||||
CFAttributedStringRef base;
|
||||
CFMutableAttributedStringRef mas;
|
||||
struct foreachParams fep;
|
||||
|
||||
cfstr = CFStringCreateWithCharacters(NULL, uiprivAttributedStringUTF16String(p->String), uiprivAttributedStringUTF16Len(p->String));
|
||||
if (cfstr == NULL) {
|
||||
// TODO
|
||||
}
|
||||
defaultAttrs = CFDictionaryCreateMutable(NULL, 0,
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
if (defaultAttrs == NULL) {
|
||||
// TODO
|
||||
}
|
||||
#if 0 /* TODO */
|
||||
ffp.desc = *(p->DefaultFont);
|
||||
defaultCTFont = fontdescToCTFont(&ffp);
|
||||
CFDictionaryAddValue(defaultAttrs, kCTFontAttributeName, defaultCTFont);
|
||||
CFRelease(defaultCTFont);
|
||||
#endif
|
||||
ps = mkParagraphStyle(p);
|
||||
CFDictionaryAddValue(defaultAttrs, kCTParagraphStyleAttributeName, ps);
|
||||
CFRelease(ps);
|
||||
|
||||
base = CFAttributedStringCreate(NULL, cfstr, defaultAttrs);
|
||||
if (base == NULL) {
|
||||
// TODO
|
||||
}
|
||||
CFRelease(cfstr);
|
||||
CFRelease(defaultAttrs);
|
||||
mas = CFAttributedStringCreateMutableCopy(NULL, 0, base);
|
||||
CFRelease(base);
|
||||
|
||||
CFAttributedStringBeginEditing(mas);
|
||||
fep.mas = mas;
|
||||
fep.backgroundParams = [NSMutableArray new];
|
||||
uiAttributedStringForEachAttribute(p->String, processAttribute, &fep);
|
||||
applyFontAttributes(mas, p->DefaultFont);
|
||||
CFAttributedStringEndEditing(mas);
|
||||
|
||||
*backgroundParams = fep.backgroundParams;
|
||||
return mas;
|
||||
}
|
||||
159
dep/libui/darwin/autolayout.m
Normal file
159
dep/libui/darwin/autolayout.m
Normal file
@@ -0,0 +1,159 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
NSLayoutConstraint *uiprivMkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc)
|
||||
{
|
||||
NSLayoutConstraint *constraint;
|
||||
|
||||
constraint = [NSLayoutConstraint constraintWithItem:view1
|
||||
attribute:attr1
|
||||
relatedBy:relation
|
||||
toItem:view2
|
||||
attribute:attr2
|
||||
multiplier:multiplier
|
||||
constant:c];
|
||||
uiprivFUTURE_NSLayoutConstraint_setIdentifier(constraint, desc);
|
||||
return constraint;
|
||||
}
|
||||
|
||||
CGFloat uiDarwinMarginAmount(void *reserved)
|
||||
{
|
||||
return 20.0;
|
||||
}
|
||||
|
||||
CGFloat uiDarwinPaddingAmount(void *reserved)
|
||||
{
|
||||
return 8.0;
|
||||
}
|
||||
|
||||
// this is needed for NSSplitView to work properly; see http://stackoverflow.com/questions/34574478/how-can-i-set-the-position-of-a-nssplitview-nowadays-setpositionofdivideratind (stal in irc.freenode.net/#macdev came up with the exact combination)
|
||||
// turns out it also works on NSTabView and NSBox too, possibly others!
|
||||
// and for bonus points, it even seems to fix unsatisfiable-constraint-autoresizing-mask issues with NSTabView and NSBox too!!! this is nuts
|
||||
void uiprivJiggleViewLayout(NSView *view)
|
||||
{
|
||||
[view setNeedsLayout:YES];
|
||||
[view layoutSubtreeIfNeeded];
|
||||
}
|
||||
|
||||
static CGFloat margins(int margined)
|
||||
{
|
||||
if (!margined)
|
||||
return 0.0;
|
||||
return uiDarwinMarginAmount(NULL);
|
||||
}
|
||||
|
||||
void uiprivSingleChildConstraintsEstablish(uiprivSingleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc)
|
||||
{
|
||||
CGFloat margin;
|
||||
|
||||
margin = margins(margined);
|
||||
|
||||
c->leadingConstraint = uiprivMkConstraint(contentView, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeLeading,
|
||||
1, -margin,
|
||||
[desc stringByAppendingString:@" leading constraint"]);
|
||||
[contentView addConstraint:c->leadingConstraint];
|
||||
[c->leadingConstraint retain];
|
||||
|
||||
c->topConstraint = uiprivMkConstraint(contentView, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeTop,
|
||||
1, -margin,
|
||||
[desc stringByAppendingString:@" top constraint"]);
|
||||
[contentView addConstraint:c->topConstraint];
|
||||
[c->topConstraint retain];
|
||||
|
||||
c->trailingConstraintGreater = uiprivMkConstraint(contentView, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationGreaterThanOrEqual,
|
||||
childView, NSLayoutAttributeTrailing,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" trailing >= constraint"]);
|
||||
if (hugsTrailing)
|
||||
[c->trailingConstraintGreater setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->trailingConstraintGreater];
|
||||
[c->trailingConstraintGreater retain];
|
||||
|
||||
c->trailingConstraintEqual = uiprivMkConstraint(contentView, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeTrailing,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" trailing == constraint"]);
|
||||
if (!hugsTrailing)
|
||||
[c->trailingConstraintEqual setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->trailingConstraintEqual];
|
||||
[c->trailingConstraintEqual retain];
|
||||
|
||||
c->bottomConstraintGreater = uiprivMkConstraint(contentView, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationGreaterThanOrEqual,
|
||||
childView, NSLayoutAttributeBottom,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" bottom >= constraint"]);
|
||||
if (hugsBottom)
|
||||
[c->bottomConstraintGreater setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->bottomConstraintGreater];
|
||||
[c->bottomConstraintGreater retain];
|
||||
|
||||
c->bottomConstraintEqual = uiprivMkConstraint(contentView, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
childView, NSLayoutAttributeBottom,
|
||||
1, margin,
|
||||
[desc stringByAppendingString:@" bottom == constraint"]);
|
||||
if (!hugsBottom)
|
||||
[c->bottomConstraintEqual setPriority:NSLayoutPriorityDefaultLow];
|
||||
[contentView addConstraint:c->bottomConstraintEqual];
|
||||
[c->bottomConstraintEqual retain];
|
||||
}
|
||||
|
||||
void uiprivSingleChildConstraintsRemove(uiprivSingleChildConstraints *c, NSView *cv)
|
||||
{
|
||||
if (c->leadingConstraint != nil) {
|
||||
[cv removeConstraint:c->leadingConstraint];
|
||||
[c->leadingConstraint release];
|
||||
c->leadingConstraint = nil;
|
||||
}
|
||||
if (c->topConstraint != nil) {
|
||||
[cv removeConstraint:c->topConstraint];
|
||||
[c->topConstraint release];
|
||||
c->topConstraint = nil;
|
||||
}
|
||||
if (c->trailingConstraintGreater != nil) {
|
||||
[cv removeConstraint:c->trailingConstraintGreater];
|
||||
[c->trailingConstraintGreater release];
|
||||
c->trailingConstraintGreater = nil;
|
||||
}
|
||||
if (c->trailingConstraintEqual != nil) {
|
||||
[cv removeConstraint:c->trailingConstraintEqual];
|
||||
[c->trailingConstraintEqual release];
|
||||
c->trailingConstraintEqual = nil;
|
||||
}
|
||||
if (c->bottomConstraintGreater != nil) {
|
||||
[cv removeConstraint:c->bottomConstraintGreater];
|
||||
[c->bottomConstraintGreater release];
|
||||
c->bottomConstraintGreater = nil;
|
||||
}
|
||||
if (c->bottomConstraintEqual != nil) {
|
||||
[cv removeConstraint:c->bottomConstraintEqual];
|
||||
[c->bottomConstraintEqual release];
|
||||
c->bottomConstraintEqual = nil;
|
||||
}
|
||||
}
|
||||
|
||||
void uiprivSingleChildConstraintsSetMargined(uiprivSingleChildConstraints *c, int margined)
|
||||
{
|
||||
CGFloat margin;
|
||||
|
||||
margin = margins(margined);
|
||||
if (c->leadingConstraint != nil)
|
||||
[c->leadingConstraint setConstant:-margin];
|
||||
if (c->topConstraint != nil)
|
||||
[c->topConstraint setConstant:-margin];
|
||||
if (c->trailingConstraintGreater != nil)
|
||||
[c->trailingConstraintGreater setConstant:margin];
|
||||
if (c->trailingConstraintEqual != nil)
|
||||
[c->trailingConstraintEqual setConstant:margin];
|
||||
if (c->bottomConstraintGreater != nil)
|
||||
[c->bottomConstraintGreater setConstant:margin];
|
||||
if (c->bottomConstraintEqual != nil)
|
||||
[c->bottomConstraintEqual setConstant:margin];
|
||||
}
|
||||
469
dep/libui/darwin/box.m
Normal file
469
dep/libui/darwin/box.m
Normal file
@@ -0,0 +1,469 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO hiding all stretchy controls still hugs trailing edge
|
||||
|
||||
@interface boxChild : NSObject
|
||||
@property uiControl *c;
|
||||
@property BOOL stretchy;
|
||||
@property NSLayoutPriority oldPrimaryHuggingPri;
|
||||
@property NSLayoutPriority oldSecondaryHuggingPri;
|
||||
- (NSView *)view;
|
||||
@end
|
||||
|
||||
@interface boxView : NSView {
|
||||
uiBox *b;
|
||||
NSMutableArray *children;
|
||||
BOOL vertical;
|
||||
int padded;
|
||||
|
||||
NSLayoutConstraint *first;
|
||||
NSMutableArray *inBetweens;
|
||||
NSLayoutConstraint *last;
|
||||
NSMutableArray *otherConstraints;
|
||||
|
||||
NSLayoutAttribute primaryStart;
|
||||
NSLayoutAttribute primaryEnd;
|
||||
NSLayoutAttribute secondaryStart;
|
||||
NSLayoutAttribute secondaryEnd;
|
||||
NSLayoutAttribute primarySize;
|
||||
NSLayoutConstraintOrientation primaryOrientation;
|
||||
NSLayoutConstraintOrientation secondaryOrientation;
|
||||
}
|
||||
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
|
||||
- (void)onDestroy;
|
||||
- (void)removeOurConstraints;
|
||||
- (void)syncEnableStates:(int)enabled;
|
||||
- (CGFloat)paddingAmount;
|
||||
- (void)establishOurConstraints;
|
||||
- (void)append:(uiControl *)c stretchy:(int)stretchy;
|
||||
- (void)delete:(int)n;
|
||||
- (int)isPadded;
|
||||
- (void)setPadded:(int)p;
|
||||
- (BOOL)hugsTrailing;
|
||||
- (BOOL)hugsBottom;
|
||||
- (int)nStretchy;
|
||||
@end
|
||||
|
||||
struct uiBox {
|
||||
uiDarwinControl c;
|
||||
boxView *view;
|
||||
};
|
||||
|
||||
@implementation boxChild
|
||||
|
||||
- (NSView *)view
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation boxView
|
||||
|
||||
- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self != nil) {
|
||||
// the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
|
||||
self->b = bb;
|
||||
self->vertical = vert;
|
||||
self->padded = 0;
|
||||
self->children = [NSMutableArray new];
|
||||
|
||||
self->inBetweens = [NSMutableArray new];
|
||||
self->otherConstraints = [NSMutableArray new];
|
||||
|
||||
if (self->vertical) {
|
||||
self->primaryStart = NSLayoutAttributeTop;
|
||||
self->primaryEnd = NSLayoutAttributeBottom;
|
||||
self->secondaryStart = NSLayoutAttributeLeading;
|
||||
self->secondaryEnd = NSLayoutAttributeTrailing;
|
||||
self->primarySize = NSLayoutAttributeHeight;
|
||||
self->primaryOrientation = NSLayoutConstraintOrientationVertical;
|
||||
self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
||||
} else {
|
||||
self->primaryStart = NSLayoutAttributeLeading;
|
||||
self->primaryEnd = NSLayoutAttributeTrailing;
|
||||
self->secondaryStart = NSLayoutAttributeTop;
|
||||
self->secondaryEnd = NSLayoutAttributeBottom;
|
||||
self->primarySize = NSLayoutAttributeWidth;
|
||||
self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
|
||||
self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
boxChild *bc;
|
||||
|
||||
[self removeOurConstraints];
|
||||
[self->inBetweens release];
|
||||
[self->otherConstraints release];
|
||||
|
||||
for (bc in self->children) {
|
||||
uiControlSetParent(bc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
|
||||
uiControlDestroy(bc.c);
|
||||
}
|
||||
[self->children release];
|
||||
}
|
||||
|
||||
- (void)removeOurConstraints
|
||||
{
|
||||
if (self->first != nil) {
|
||||
[self removeConstraint:self->first];
|
||||
[self->first release];
|
||||
self->first = nil;
|
||||
}
|
||||
if ([self->inBetweens count] != 0) {
|
||||
[self removeConstraints:self->inBetweens];
|
||||
[self->inBetweens removeAllObjects];
|
||||
}
|
||||
if (self->last != nil) {
|
||||
[self removeConstraint:self->last];
|
||||
[self->last release];
|
||||
self->last = nil;
|
||||
}
|
||||
if ([self->otherConstraints count] != 0) {
|
||||
[self removeConstraints:self->otherConstraints];
|
||||
[self->otherConstraints removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)syncEnableStates:(int)enabled
|
||||
{
|
||||
boxChild *bc;
|
||||
|
||||
for (bc in self->children)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
|
||||
}
|
||||
|
||||
- (CGFloat)paddingAmount
|
||||
{
|
||||
if (!self->padded)
|
||||
return 0.0;
|
||||
return uiDarwinPaddingAmount(NULL);
|
||||
}
|
||||
|
||||
- (void)establishOurConstraints
|
||||
{
|
||||
boxChild *bc;
|
||||
CGFloat padding;
|
||||
NSView *prev;
|
||||
NSLayoutConstraint *c;
|
||||
BOOL (*hugsSecondary)(uiDarwinControl *);
|
||||
|
||||
[self removeOurConstraints];
|
||||
if ([self->children count] == 0)
|
||||
return;
|
||||
padding = [self paddingAmount];
|
||||
|
||||
// first arrange in the primary direction
|
||||
prev = nil;
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
if (prev == nil) { // first view
|
||||
self->first = uiprivMkConstraint(self, self->primaryStart,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->primaryStart,
|
||||
1, 0,
|
||||
@"uiBox first primary constraint");
|
||||
[self addConstraint:self->first];
|
||||
[self->first retain];
|
||||
prev = [bc view];
|
||||
continue;
|
||||
}
|
||||
// not the first; link it
|
||||
c = uiprivMkConstraint(prev, self->primaryEnd,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->primaryStart,
|
||||
1, -padding,
|
||||
@"uiBox in-between primary constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
prev = [bc view];
|
||||
}
|
||||
if (prev == nil) // no control visible; act as if no controls
|
||||
return;
|
||||
self->last = uiprivMkConstraint(prev, self->primaryEnd,
|
||||
NSLayoutRelationEqual,
|
||||
self, self->primaryEnd,
|
||||
1, 0,
|
||||
@"uiBox last primary constraint");
|
||||
[self addConstraint:self->last];
|
||||
[self->last retain];
|
||||
|
||||
// then arrange in the secondary direction
|
||||
hugsSecondary = uiDarwinControlHugsTrailingEdge;
|
||||
if (!self->vertical)
|
||||
hugsSecondary = uiDarwinControlHugsBottom;
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
c = uiprivMkConstraint(self, self->secondaryStart,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->secondaryStart,
|
||||
1, 0,
|
||||
@"uiBox secondary start constraint");
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
c = uiprivMkConstraint([bc view], self->secondaryEnd,
|
||||
NSLayoutRelationLessThanOrEqual,
|
||||
self, self->secondaryEnd,
|
||||
1, 0,
|
||||
@"uiBox secondary end <= constraint");
|
||||
if ((*hugsSecondary)(uiDarwinControl(bc.c)))
|
||||
[c setPriority:NSLayoutPriorityDefaultLow];
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
c = uiprivMkConstraint([bc view], self->secondaryEnd,
|
||||
NSLayoutRelationEqual,
|
||||
self, self->secondaryEnd,
|
||||
1, 0,
|
||||
@"uiBox secondary end == constraint");
|
||||
if (!(*hugsSecondary)(uiDarwinControl(bc.c)))
|
||||
[c setPriority:NSLayoutPriorityDefaultLow];
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
}
|
||||
|
||||
// and make all stretchy controls the same size
|
||||
if ([self nStretchy] == 0)
|
||||
return;
|
||||
prev = nil; // first stretchy view
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
if (!bc.stretchy)
|
||||
continue;
|
||||
if (prev == nil) {
|
||||
prev = [bc view];
|
||||
continue;
|
||||
}
|
||||
c = uiprivMkConstraint(prev, self->primarySize,
|
||||
NSLayoutRelationEqual,
|
||||
[bc view], self->primarySize,
|
||||
1, 0,
|
||||
@"uiBox stretchy size constraint");
|
||||
[self addConstraint:c];
|
||||
[self->otherConstraints addObject:c];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)append:(uiControl *)c stretchy:(int)stretchy
|
||||
{
|
||||
boxChild *bc;
|
||||
NSLayoutPriority priority;
|
||||
int oldnStretchy;
|
||||
|
||||
bc = [boxChild new];
|
||||
bc.c = c;
|
||||
bc.stretchy = stretchy;
|
||||
bc.oldPrimaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->primaryOrientation);
|
||||
bc.oldSecondaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->secondaryOrientation);
|
||||
|
||||
uiControlSetParent(bc.c, uiControl(self->b));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));
|
||||
|
||||
// if a control is stretchy, it should not hug in the primary direction
|
||||
// otherwise, it should *forcibly* hug
|
||||
if (bc.stretchy)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
// LONGTERM will default high work?
|
||||
priority = NSLayoutPriorityRequired;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation);
|
||||
// make sure controls don't hug their secondary direction so they fill the width of the view
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation);
|
||||
|
||||
oldnStretchy = [self nStretchy];
|
||||
[self->children addObject:bc];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (bc.stretchy)
|
||||
if (oldnStretchy == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
|
||||
|
||||
[bc release]; // we don't need the initial reference now
|
||||
}
|
||||
|
||||
- (void)delete:(int)n
|
||||
{
|
||||
boxChild *bc;
|
||||
int stretchy;
|
||||
|
||||
bc = (boxChild *) [self->children objectAtIndex:n];
|
||||
stretchy = bc.stretchy;
|
||||
|
||||
uiControlSetParent(bc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
|
||||
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldPrimaryHuggingPri, self->primaryOrientation);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldSecondaryHuggingPri, self->secondaryOrientation);
|
||||
|
||||
[self->children removeObjectAtIndex:n];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (stretchy)
|
||||
if ([self nStretchy] == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
|
||||
}
|
||||
|
||||
- (int)isPadded
|
||||
{
|
||||
return self->padded;
|
||||
}
|
||||
|
||||
- (void)setPadded:(int)p
|
||||
{
|
||||
CGFloat padding;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
self->padded = p;
|
||||
padding = [self paddingAmount];
|
||||
for (c in self->inBetweens)
|
||||
[c setConstant:-padding];
|
||||
}
|
||||
|
||||
- (BOOL)hugsTrailing
|
||||
{
|
||||
if (self->vertical) // always hug if vertical
|
||||
return YES;
|
||||
return [self nStretchy] != 0;
|
||||
}
|
||||
|
||||
- (BOOL)hugsBottom
|
||||
{
|
||||
if (!self->vertical) // always hug if horizontal
|
||||
return YES;
|
||||
return [self nStretchy] != 0;
|
||||
}
|
||||
|
||||
- (int)nStretchy
|
||||
{
|
||||
boxChild *bc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (bc in self->children) {
|
||||
if (!uiControlVisible(bc.c))
|
||||
continue;
|
||||
if (bc.stretchy)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiBoxDestroy(uiControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
[b->view onDestroy];
|
||||
[b->view release];
|
||||
uiFreeControl(uiControl(b));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiBox, view)
|
||||
uiDarwinControlDefaultParent(uiBox, view)
|
||||
uiDarwinControlDefaultSetParent(uiBox, view)
|
||||
uiDarwinControlDefaultToplevel(uiBox, view)
|
||||
uiDarwinControlDefaultVisible(uiBox, view)
|
||||
uiDarwinControlDefaultShow(uiBox, view)
|
||||
uiDarwinControlDefaultHide(uiBox, view)
|
||||
uiDarwinControlDefaultEnabled(uiBox, view)
|
||||
uiDarwinControlDefaultEnable(uiBox, view)
|
||||
uiDarwinControlDefaultDisable(uiBox, view)
|
||||
|
||||
static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
|
||||
return;
|
||||
[b->view syncEnableStates:enabled];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiBox, view)
|
||||
|
||||
static BOOL uiBoxHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
return [b->view hugsTrailing];
|
||||
}
|
||||
|
||||
static BOOL uiBoxHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
return [b->view hugsBottom];
|
||||
}
|
||||
|
||||
static void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
[b->view establishOurConstraints];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHuggingPriority(uiBox, view)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiBox, view)
|
||||
|
||||
static void uiBoxChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiBox *b = uiBox(c);
|
||||
|
||||
[b->view establishOurConstraints];
|
||||
}
|
||||
|
||||
void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
|
||||
{
|
||||
// LONGTERM on other platforms
|
||||
// or at leat allow this and implicitly turn it into a spacer
|
||||
if (c == NULL)
|
||||
uiprivUserBug("You cannot add NULL to a uiBox.");
|
||||
[b->view append:c stretchy:stretchy];
|
||||
}
|
||||
|
||||
void uiBoxDelete(uiBox *b, int n)
|
||||
{
|
||||
[b->view delete:n];
|
||||
}
|
||||
|
||||
int uiBoxPadded(uiBox *b)
|
||||
{
|
||||
return [b->view isPadded];
|
||||
}
|
||||
|
||||
void uiBoxSetPadded(uiBox *b, int padded)
|
||||
{
|
||||
[b->view setPadded:padded];
|
||||
}
|
||||
|
||||
static uiBox *finishNewBox(BOOL vertical)
|
||||
{
|
||||
uiBox *b;
|
||||
|
||||
uiDarwinNewControl(uiBox, b);
|
||||
|
||||
b->view = [[boxView alloc] initWithVertical:vertical b:b];
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
uiBox *uiNewHorizontalBox(void)
|
||||
{
|
||||
return finishNewBox(NO);
|
||||
}
|
||||
|
||||
uiBox *uiNewVerticalBox(void)
|
||||
{
|
||||
return finishNewBox(YES);
|
||||
}
|
||||
113
dep/libui/darwin/button.m
Normal file
113
dep/libui/darwin/button.m
Normal file
@@ -0,0 +1,113 @@
|
||||
// 13 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiButton {
|
||||
uiDarwinControl c;
|
||||
NSButton *button;
|
||||
void (*onClicked)(uiButton *, void *);
|
||||
void *onClickedData;
|
||||
};
|
||||
|
||||
@interface buttonDelegateClass : NSObject {
|
||||
uiprivMap *buttons;
|
||||
}
|
||||
- (IBAction)onClicked:(id)sender;
|
||||
- (void)registerButton:(uiButton *)b;
|
||||
- (void)unregisterButton:(uiButton *)b;
|
||||
@end
|
||||
|
||||
@implementation buttonDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->buttons = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->buttons);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onClicked:(id)sender
|
||||
{
|
||||
uiButton *b;
|
||||
|
||||
b = (uiButton *) uiprivMapGet(self->buttons, sender);
|
||||
(*(b->onClicked))(b, b->onClickedData);
|
||||
}
|
||||
|
||||
- (void)registerButton:(uiButton *)b
|
||||
{
|
||||
uiprivMapSet(self->buttons, b->button, b);
|
||||
[b->button setTarget:self];
|
||||
[b->button setAction:@selector(onClicked:)];
|
||||
}
|
||||
|
||||
- (void)unregisterButton:(uiButton *)b
|
||||
{
|
||||
[b->button setTarget:nil];
|
||||
uiprivMapDelete(self->buttons, b->button);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static buttonDelegateClass *buttonDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiButton, button)
|
||||
|
||||
static void uiButtonDestroy(uiControl *c)
|
||||
{
|
||||
uiButton *b = uiButton(c);
|
||||
|
||||
[buttonDelegate unregisterButton:b];
|
||||
[b->button release];
|
||||
uiFreeControl(uiControl(b));
|
||||
}
|
||||
|
||||
char *uiButtonText(uiButton *b)
|
||||
{
|
||||
return uiDarwinNSStringToText([b->button title]);
|
||||
}
|
||||
|
||||
void uiButtonSetText(uiButton *b, const char *text)
|
||||
{
|
||||
[b->button setTitle:uiprivToNSString(text)];
|
||||
}
|
||||
|
||||
void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
|
||||
{
|
||||
b->onClicked = f;
|
||||
b->onClickedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnClicked(uiButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiButton *uiNewButton(const char *text)
|
||||
{
|
||||
uiButton *b;
|
||||
|
||||
uiDarwinNewControl(uiButton, b);
|
||||
|
||||
b->button = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[b->button setTitle:uiprivToNSString(text)];
|
||||
[b->button setButtonType:NSMomentaryPushInButton];
|
||||
[b->button setBordered:YES];
|
||||
[b->button setBezelStyle:NSRoundedBezelStyle];
|
||||
uiDarwinSetControlFont(b->button, NSRegularControlSize);
|
||||
|
||||
if (buttonDelegate == nil) {
|
||||
buttonDelegate = [[buttonDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:buttonDelegate];
|
||||
}
|
||||
[buttonDelegate registerButton:b];
|
||||
uiButtonOnClicked(b, defaultOnClicked, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
129
dep/libui/darwin/checkbox.m
Normal file
129
dep/libui/darwin/checkbox.m
Normal file
@@ -0,0 +1,129 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiCheckbox {
|
||||
uiDarwinControl c;
|
||||
NSButton *button;
|
||||
void (*onToggled)(uiCheckbox *, void *);
|
||||
void *onToggledData;
|
||||
};
|
||||
|
||||
@interface checkboxDelegateClass : NSObject {
|
||||
uiprivMap *buttons;
|
||||
}
|
||||
- (IBAction)onToggled:(id)sender;
|
||||
- (void)registerCheckbox:(uiCheckbox *)c;
|
||||
- (void)unregisterCheckbox:(uiCheckbox *)c;
|
||||
@end
|
||||
|
||||
@implementation checkboxDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->buttons = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->buttons);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onToggled:(id)sender
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
c = (uiCheckbox *) uiprivMapGet(self->buttons, sender);
|
||||
(*(c->onToggled))(c, c->onToggledData);
|
||||
}
|
||||
|
||||
- (void)registerCheckbox:(uiCheckbox *)c
|
||||
{
|
||||
uiprivMapSet(self->buttons, c->button, c);
|
||||
[c->button setTarget:self];
|
||||
[c->button setAction:@selector(onToggled:)];
|
||||
}
|
||||
|
||||
- (void)unregisterCheckbox:(uiCheckbox *)c
|
||||
{
|
||||
[c->button setTarget:nil];
|
||||
uiprivMapDelete(self->buttons, c->button);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static checkboxDelegateClass *checkboxDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiCheckbox, button)
|
||||
|
||||
static void uiCheckboxDestroy(uiControl *cc)
|
||||
{
|
||||
uiCheckbox *c = uiCheckbox(cc);
|
||||
|
||||
[checkboxDelegate unregisterCheckbox:c];
|
||||
[c->button release];
|
||||
uiFreeControl(uiControl(c));
|
||||
}
|
||||
|
||||
char *uiCheckboxText(uiCheckbox *c)
|
||||
{
|
||||
return uiDarwinNSStringToText([c->button title]);
|
||||
}
|
||||
|
||||
void uiCheckboxSetText(uiCheckbox *c, const char *text)
|
||||
{
|
||||
[c->button setTitle:uiprivToNSString(text)];
|
||||
}
|
||||
|
||||
void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
|
||||
{
|
||||
c->onToggled = f;
|
||||
c->onToggledData = data;
|
||||
}
|
||||
|
||||
int uiCheckboxChecked(uiCheckbox *c)
|
||||
{
|
||||
return [c->button state] == NSOnState;
|
||||
}
|
||||
|
||||
void uiCheckboxSetChecked(uiCheckbox *c, int checked)
|
||||
{
|
||||
NSInteger state;
|
||||
|
||||
state = NSOnState;
|
||||
if (!checked)
|
||||
state = NSOffState;
|
||||
[c->button setState:state];
|
||||
}
|
||||
|
||||
static void defaultOnToggled(uiCheckbox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiCheckbox *uiNewCheckbox(const char *text)
|
||||
{
|
||||
uiCheckbox *c;
|
||||
|
||||
uiDarwinNewControl(uiCheckbox, c);
|
||||
|
||||
c->button = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[c->button setTitle:uiprivToNSString(text)];
|
||||
[c->button setButtonType:NSSwitchButton];
|
||||
// doesn't seem to have an associated bezel style
|
||||
[c->button setBordered:NO];
|
||||
[c->button setTransparent:NO];
|
||||
uiDarwinSetControlFont(c->button, NSRegularControlSize);
|
||||
|
||||
if (checkboxDelegate == nil) {
|
||||
checkboxDelegate = [[checkboxDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:checkboxDelegate];
|
||||
}
|
||||
[checkboxDelegate registerCheckbox:c];
|
||||
uiCheckboxOnToggled(c, defaultOnToggled, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
159
dep/libui/darwin/colorbutton.m
Normal file
159
dep/libui/darwin/colorbutton.m
Normal file
@@ -0,0 +1,159 @@
|
||||
// 15 may 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO no intrinsic height?
|
||||
|
||||
@interface colorButton : NSColorWell {
|
||||
uiColorButton *libui_b;
|
||||
BOOL libui_changing;
|
||||
BOOL libui_setting;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b;
|
||||
- (void)deactivateOnClose:(NSNotification *)note;
|
||||
- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a;
|
||||
- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a;
|
||||
@end
|
||||
|
||||
// only one may be active at one time
|
||||
static colorButton *activeColorButton = nil;
|
||||
|
||||
struct uiColorButton {
|
||||
uiDarwinControl c;
|
||||
colorButton *button;
|
||||
void (*onChanged)(uiColorButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@implementation colorButton
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
// the default color is white; set it to black first (see -setColor: below for why we do it first)
|
||||
[self libuiSetColor:0.0 g:0.0 b:0.0 a:1.0];
|
||||
|
||||
self->libui_b = b;
|
||||
self->libui_changing = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)activate:(BOOL)exclusive
|
||||
{
|
||||
if (activeColorButton != nil)
|
||||
activeColorButton->libui_changing = YES;
|
||||
[NSColorPanel setPickerMask:NSColorPanelAllModesMask];
|
||||
[[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
|
||||
[super activate:YES];
|
||||
activeColorButton = self;
|
||||
// see stddialogs.m for details
|
||||
[[NSColorPanel sharedColorPanel] setWorksWhenModal:NO];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deactivateOnClose:)
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSColorPanel sharedColorPanel]];
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
[super deactivate];
|
||||
activeColorButton = nil;
|
||||
if (!self->libui_changing)
|
||||
[[NSColorPanel sharedColorPanel] orderOut:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSColorPanel sharedColorPanel]];
|
||||
self->libui_changing = NO;
|
||||
}
|
||||
|
||||
- (void)deactivateOnClose:(NSNotification *)note
|
||||
{
|
||||
[self deactivate];
|
||||
}
|
||||
|
||||
- (void)setColor:(NSColor *)color
|
||||
{
|
||||
uiColorButton *b = self->libui_b;
|
||||
|
||||
[super setColor:color];
|
||||
// this is called by NSColorWell's init, so we have to guard
|
||||
// also don't signal during a programmatic change
|
||||
if (b != nil && !self->libui_setting)
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a
|
||||
{
|
||||
NSColor *rgba;
|
||||
CGFloat cr, cg, cb, ca;
|
||||
|
||||
// the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception
|
||||
rgba = [[self color] colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
[rgba getRed:&cr green:&cg blue:&cb alpha:&ca];
|
||||
*r = cr;
|
||||
*g = cg;
|
||||
*b = cb;
|
||||
*a = ca;
|
||||
// rgba will be autoreleased since it isn't a new or init call
|
||||
}
|
||||
|
||||
- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a
|
||||
{
|
||||
self->libui_setting = YES;
|
||||
[self setColor:[NSColor colorWithSRGBRed:r green:g blue:b alpha:a]];
|
||||
self->libui_setting = NO;
|
||||
}
|
||||
|
||||
// NSColorWell has no intrinsic size by default; give it the default Interface Builder size.
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
return NSMakeSize(44, 23);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaults(uiColorButton, button)
|
||||
|
||||
// we do not want color change events to be sent to any controls other than the color buttons
|
||||
// see main.m for more details
|
||||
BOOL uiprivColorButtonInhibitSendAction(SEL sel, id from, id to)
|
||||
{
|
||||
if (sel != @selector(changeColor:))
|
||||
return NO;
|
||||
return ![to isKindOfClass:[colorButton class]];
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiColorButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
|
||||
{
|
||||
[b->button libuiColor:r g:g b:bl a:a];
|
||||
}
|
||||
|
||||
void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
|
||||
{
|
||||
[b->button libuiSetColor:r g:g b:bl a:a];
|
||||
}
|
||||
|
||||
void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiColorButton *uiNewColorButton(void)
|
||||
{
|
||||
uiColorButton *b;
|
||||
|
||||
uiDarwinNewControl(uiColorButton, b);
|
||||
|
||||
b->button = [[colorButton alloc] initWithFrame:NSZeroRect libuiColorButton:b];
|
||||
|
||||
uiColorButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
145
dep/libui/darwin/combobox.m
Normal file
145
dep/libui/darwin/combobox.m
Normal file
@@ -0,0 +1,145 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
// NSPopUpButton is fine.
|
||||
#define comboboxWidth 96
|
||||
|
||||
struct uiCombobox {
|
||||
uiDarwinControl c;
|
||||
NSPopUpButton *pb;
|
||||
NSArrayController *pbac;
|
||||
void (*onSelected)(uiCombobox *, void *);
|
||||
void *onSelectedData;
|
||||
};
|
||||
|
||||
@interface comboboxDelegateClass : NSObject {
|
||||
uiprivMap *comboboxes;
|
||||
}
|
||||
- (IBAction)onSelected:(id)sender;
|
||||
- (void)registerCombobox:(uiCombobox *)c;
|
||||
- (void)unregisterCombobox:(uiCombobox *)c;
|
||||
@end
|
||||
|
||||
@implementation comboboxDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->comboboxes = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->comboboxes);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onSelected:(id)sender
|
||||
{
|
||||
uiCombobox *c;
|
||||
|
||||
c = uiCombobox(uiprivMapGet(self->comboboxes, sender));
|
||||
(*(c->onSelected))(c, c->onSelectedData);
|
||||
}
|
||||
|
||||
- (void)registerCombobox:(uiCombobox *)c
|
||||
{
|
||||
uiprivMapSet(self->comboboxes, c->pb, c);
|
||||
[c->pb setTarget:self];
|
||||
[c->pb setAction:@selector(onSelected:)];
|
||||
}
|
||||
|
||||
- (void)unregisterCombobox:(uiCombobox *)c
|
||||
{
|
||||
[c->pb setTarget:nil];
|
||||
uiprivMapDelete(self->comboboxes, c->pb);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static comboboxDelegateClass *comboboxDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiCombobox, pb)
|
||||
|
||||
static void uiComboboxDestroy(uiControl *cc)
|
||||
{
|
||||
uiCombobox *c = uiCombobox(cc);
|
||||
|
||||
[comboboxDelegate unregisterCombobox:c];
|
||||
[c->pb unbind:@"contentObjects"];
|
||||
[c->pb unbind:@"selectedIndex"];
|
||||
[c->pbac release];
|
||||
[c->pb release];
|
||||
uiFreeControl(uiControl(c));
|
||||
}
|
||||
|
||||
void uiComboboxAppend(uiCombobox *c, const char *text)
|
||||
{
|
||||
[c->pbac addObject:uiprivToNSString(text)];
|
||||
}
|
||||
|
||||
int uiComboboxSelected(uiCombobox *c)
|
||||
{
|
||||
return [c->pb indexOfSelectedItem];
|
||||
}
|
||||
|
||||
void uiComboboxSetSelected(uiCombobox *c, int n)
|
||||
{
|
||||
[c->pb selectItemAtIndex:n];
|
||||
}
|
||||
|
||||
void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onSelected = f;
|
||||
c->onSelectedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnSelected(uiCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiCombobox *uiNewCombobox(void)
|
||||
{
|
||||
uiCombobox *c;
|
||||
NSPopUpButtonCell *pbcell;
|
||||
|
||||
uiDarwinNewControl(uiCombobox, c);
|
||||
|
||||
c->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
|
||||
[c->pb setPreferredEdge:NSMinYEdge];
|
||||
pbcell = (NSPopUpButtonCell *) [c->pb cell];
|
||||
[pbcell setArrowPosition:NSPopUpArrowAtBottom];
|
||||
// the font defined by Interface Builder is Menu 13, which is lol
|
||||
// just use the regular control size for consistency
|
||||
uiDarwinSetControlFont(c->pb, NSRegularControlSize);
|
||||
|
||||
// NSPopUpButton doesn't work like a combobox
|
||||
// - it automatically selects the first item
|
||||
// - it doesn't support duplicates
|
||||
// but we can use a NSArrayController and Cocoa bindings to bypass these restrictions
|
||||
c->pbac = [NSArrayController new];
|
||||
[c->pbac setAvoidsEmptySelection:NO];
|
||||
[c->pbac setSelectsInsertedObjects:NO];
|
||||
[c->pbac setAutomaticallyRearrangesObjects:NO];
|
||||
[c->pb bind:@"contentValues"
|
||||
toObject:c->pbac
|
||||
withKeyPath:@"arrangedObjects"
|
||||
options:nil];
|
||||
[c->pb bind:@"selectedIndex"
|
||||
toObject:c->pbac
|
||||
withKeyPath:@"selectionIndex"
|
||||
options:nil];
|
||||
|
||||
if (comboboxDelegate == nil) {
|
||||
comboboxDelegate = [[comboboxDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:comboboxDelegate];
|
||||
}
|
||||
[comboboxDelegate registerCombobox:c];
|
||||
uiComboboxOnSelected(c, defaultOnSelected, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
84
dep/libui/darwin/control.m
Normal file
84
dep/libui/darwin/control.m
Normal file
@@ -0,0 +1,84 @@
|
||||
// 16 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
void uiDarwinControlSyncEnableState(uiDarwinControl *c, int state)
|
||||
{
|
||||
(*(c->SyncEnableState))(c, state);
|
||||
}
|
||||
|
||||
void uiDarwinControlSetSuperview(uiDarwinControl *c, NSView *superview)
|
||||
{
|
||||
(*(c->SetSuperview))(c, superview);
|
||||
}
|
||||
|
||||
BOOL uiDarwinControlHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
return (*(c->HugsTrailingEdge))(c);
|
||||
}
|
||||
|
||||
BOOL uiDarwinControlHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
return (*(c->HugsBottom))(c);
|
||||
}
|
||||
|
||||
void uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
(*(c->ChildEdgeHuggingChanged))(c);
|
||||
}
|
||||
|
||||
NSLayoutPriority uiDarwinControlHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
return (*(c->HuggingPriority))(c, orientation);
|
||||
}
|
||||
|
||||
void uiDarwinControlSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
(*(c->SetHuggingPriority))(c, priority, orientation);
|
||||
}
|
||||
|
||||
void uiDarwinControlChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
(*(c->ChildVisibilityChanged))(c);
|
||||
}
|
||||
|
||||
void uiDarwinSetControlFont(NSControl *c, NSControlSize size)
|
||||
{
|
||||
[c setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];
|
||||
}
|
||||
|
||||
#define uiDarwinControlSignature 0x44617277
|
||||
|
||||
uiDarwinControl *uiDarwinAllocControl(size_t n, uint32_t typesig, const char *typenamestr)
|
||||
{
|
||||
return uiDarwinControl(uiAllocControl(n, uiDarwinControlSignature, typesig, typenamestr));
|
||||
}
|
||||
|
||||
BOOL uiDarwinShouldStopSyncEnableState(uiDarwinControl *c, BOOL enabled)
|
||||
{
|
||||
int ce;
|
||||
|
||||
ce = uiControlEnabled(uiControl(c));
|
||||
// only stop if we're going from disabled back to enabled; don't stop under any other condition
|
||||
// (if we stop when going from enabled to disabled then enabled children of a disabled control won't get disabled at the OS level)
|
||||
if (!ce && enabled)
|
||||
return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
void uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiControl *parent;
|
||||
|
||||
parent = uiControlParent(uiControl(c));
|
||||
if (parent != NULL)
|
||||
uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl(parent));
|
||||
}
|
||||
|
||||
void uiDarwinNotifyVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiControl *parent;
|
||||
|
||||
parent = uiControlParent(uiControl(c));
|
||||
if (parent != NULL)
|
||||
uiDarwinControlChildVisibilityChanged(uiDarwinControl(parent));
|
||||
}
|
||||
172
dep/libui/darwin/datetimepicker.m
Normal file
172
dep/libui/darwin/datetimepicker.m
Normal file
@@ -0,0 +1,172 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiDateTimePicker {
|
||||
uiDarwinControl c;
|
||||
NSDatePicker *dp;
|
||||
void (*onChanged)(uiDateTimePicker *, void *);
|
||||
void *onChangedData;
|
||||
BOOL blockSendOnce;
|
||||
};
|
||||
|
||||
// TODO see if target-action works here or not; I forgot what cody271@ originally said
|
||||
// the primary advantage of the delegate is the ability to reject changes, but libui doesn't support that yet — we should consider that API option as well
|
||||
@interface uiprivDatePickerDelegateClass : NSObject<NSDatePickerCellDelegate> {
|
||||
uiprivMap *pickers;
|
||||
}
|
||||
- (void)datePickerCell:(NSDatePickerCell *)aDatePickerCell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval;
|
||||
- (void)doTimer:(NSTimer *)timer;
|
||||
- (void)registerPicker:(uiDateTimePicker *)d;
|
||||
- (void)unregisterPicker:(uiDateTimePicker *)d;
|
||||
@end
|
||||
|
||||
@implementation uiprivDatePickerDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->pickers = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->pickers);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)datePickerCell:(NSDatePickerCell *)cell validateProposedDateValue:(NSDate **)proposedDateValue timeInterval:(NSTimeInterval *)proposedTimeInterval
|
||||
{
|
||||
uiDateTimePicker *d;
|
||||
|
||||
d = (uiDateTimePicker *) uiprivMapGet(self->pickers, cell);
|
||||
[NSTimer scheduledTimerWithTimeInterval:0
|
||||
target:self
|
||||
selector:@selector(doTimer:)
|
||||
userInfo:[NSValue valueWithPointer:d]
|
||||
repeats:NO];
|
||||
}
|
||||
|
||||
- (void)doTimer:(NSTimer *)timer
|
||||
{
|
||||
NSValue *v;
|
||||
uiDateTimePicker *d;
|
||||
|
||||
v = (NSValue *) [timer userInfo];
|
||||
d = (uiDateTimePicker *) [v pointerValue];
|
||||
if (d->blockSendOnce) {
|
||||
d->blockSendOnce = NO;
|
||||
return;
|
||||
}
|
||||
(*(d->onChanged))(d, d->onChangedData);
|
||||
}
|
||||
|
||||
- (void)registerPicker:(uiDateTimePicker *)d
|
||||
{
|
||||
uiprivMapSet(self->pickers, d->dp.cell, d);
|
||||
[d->dp setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterPicker:(uiDateTimePicker *)d
|
||||
{
|
||||
[d->dp setDelegate:nil];
|
||||
uiprivMapDelete(self->pickers, d->dp.cell);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static uiprivDatePickerDelegateClass *datePickerDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiDateTimePicker, dp)
|
||||
|
||||
static void uiDateTimePickerDestroy(uiControl *c)
|
||||
{
|
||||
uiDateTimePicker *d = uiDateTimePicker(c);
|
||||
|
||||
[datePickerDelegate unregisterPicker:d];
|
||||
[d->dp release];
|
||||
uiFreeControl(uiControl(d));
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiDateTimePicker *d, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// TODO consider using NSDateComponents iff we ever need the extra accuracy of not using NSTimeInterval
|
||||
void uiDateTimePickerTime(uiDateTimePicker *d, struct tm *time)
|
||||
{
|
||||
time_t t;
|
||||
struct tm tmbuf;
|
||||
NSDate *date;
|
||||
|
||||
date = [d->dp dateValue];
|
||||
t = (time_t) [date timeIntervalSince1970];
|
||||
|
||||
// Copy time to minimize a race condition
|
||||
// time.h functions use global non-thread-safe data
|
||||
tmbuf = *localtime(&t);
|
||||
memcpy(time, &tmbuf, sizeof (struct tm));
|
||||
}
|
||||
|
||||
void uiDateTimePickerSetTime(uiDateTimePicker *d, const struct tm *time)
|
||||
{
|
||||
time_t t;
|
||||
struct tm tmbuf;
|
||||
|
||||
// Copy time because mktime() modifies its argument
|
||||
memcpy(&tmbuf, time, sizeof (struct tm));
|
||||
t = mktime(&tmbuf);
|
||||
|
||||
// TODO get rid of the need for this
|
||||
d->blockSendOnce = YES;
|
||||
[d->dp setDateValue:[NSDate dateWithTimeIntervalSince1970:t]];
|
||||
}
|
||||
|
||||
void uiDateTimePickerOnChanged(uiDateTimePicker *d, void (*f)(uiDateTimePicker *, void *), void *data)
|
||||
{
|
||||
d->onChanged = f;
|
||||
d->onChangedData = data;
|
||||
}
|
||||
|
||||
static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elements)
|
||||
{
|
||||
uiDateTimePicker *d;
|
||||
|
||||
uiDarwinNewControl(uiDateTimePicker, d);
|
||||
|
||||
d->dp = [[NSDatePicker alloc] initWithFrame:NSZeroRect];
|
||||
[d->dp setDateValue:[NSDate date]];
|
||||
[d->dp setBordered:NO];
|
||||
[d->dp setBezeled:YES];
|
||||
[d->dp setDrawsBackground:YES];
|
||||
[d->dp setDatePickerStyle:NSTextFieldAndStepperDatePickerStyle];
|
||||
[d->dp setDatePickerElements:elements];
|
||||
[d->dp setDatePickerMode:NSSingleDateMode];
|
||||
uiDarwinSetControlFont(d->dp, NSRegularControlSize);
|
||||
|
||||
if (datePickerDelegate == nil) {
|
||||
datePickerDelegate = [[uiprivDatePickerDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:datePickerDelegate];
|
||||
}
|
||||
[datePickerDelegate registerPicker:d];
|
||||
uiDateTimePickerOnChanged(d, defaultOnChanged, NULL);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDateTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag | NSHourMinuteSecondDatePickerElementFlag);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewDatePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag);
|
||||
}
|
||||
|
||||
uiDateTimePicker *uiNewTimePicker(void)
|
||||
{
|
||||
return finishNewDateTimePicker(NSHourMinuteSecondDatePickerElementFlag);
|
||||
}
|
||||
19
dep/libui/darwin/debug.m
Normal file
19
dep/libui/darwin/debug.m
Normal file
@@ -0,0 +1,19 @@
|
||||
// 13 may 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// LONGTERM don't halt on release builds
|
||||
|
||||
void uiprivRealBug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
|
||||
{
|
||||
NSMutableString *str;
|
||||
NSString *formatted;
|
||||
|
||||
str = [NSMutableString new];
|
||||
[str appendString:[NSString stringWithFormat:@"[libui] %s:%s:%s() %s", file, line, func, prefix]];
|
||||
formatted = [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:ap];
|
||||
[str appendString:formatted];
|
||||
[formatted release];
|
||||
NSLog(@"%@", str);
|
||||
[str release];
|
||||
__builtin_trap();
|
||||
}
|
||||
8
dep/libui/darwin/draw.h
Normal file
8
dep/libui/darwin/draw.h
Normal file
@@ -0,0 +1,8 @@
|
||||
// 6 january 2017
|
||||
|
||||
// TODO why do we still have this file; should we just split draw.m or not
|
||||
|
||||
struct uiDrawContext {
|
||||
CGContextRef c;
|
||||
CGFloat height; // needed for text; see below
|
||||
};
|
||||
449
dep/libui/darwin/draw.m
Normal file
449
dep/libui/darwin/draw.m
Normal file
@@ -0,0 +1,449 @@
|
||||
// 6 september 2015
|
||||
#import "uipriv_darwin.h"
|
||||
#import "draw.h"
|
||||
|
||||
struct uiDrawPath {
|
||||
CGMutablePathRef path;
|
||||
uiDrawFillMode fillMode;
|
||||
BOOL ended;
|
||||
};
|
||||
|
||||
uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
|
||||
{
|
||||
uiDrawPath *p;
|
||||
|
||||
p = uiprivNew(uiDrawPath);
|
||||
p->path = CGPathCreateMutable();
|
||||
p->fillMode = mode;
|
||||
return p;
|
||||
}
|
||||
|
||||
void uiDrawFreePath(uiDrawPath *p)
|
||||
{
|
||||
CGPathRelease((CGPathRef) (p->path));
|
||||
uiprivFree(p);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
if (p->ended)
|
||||
uiprivUserBug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
|
||||
CGPathMoveToPoint(p->path, NULL, x, y);
|
||||
}
|
||||
|
||||
void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
||||
{
|
||||
double sinStart, cosStart;
|
||||
double startx, starty;
|
||||
|
||||
if (p->ended)
|
||||
uiprivUserBug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
|
||||
sinStart = sin(startAngle);
|
||||
cosStart = cos(startAngle);
|
||||
startx = xCenter + radius * cosStart;
|
||||
starty = yCenter + radius * sinStart;
|
||||
CGPathMoveToPoint(p->path, NULL, startx, starty);
|
||||
uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);
|
||||
}
|
||||
|
||||
void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
|
||||
{
|
||||
// TODO refine this to require being in a path
|
||||
if (p->ended)
|
||||
uiprivImplBug("attempt to add line to ended path in uiDrawPathLineTo()");
|
||||
CGPathAddLineToPoint(p->path, NULL, x, y);
|
||||
}
|
||||
|
||||
void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
|
||||
{
|
||||
bool cw;
|
||||
|
||||
// TODO likewise
|
||||
if (p->ended)
|
||||
uiprivImplBug("attempt to add arc to ended path in uiDrawPathArcTo()");
|
||||
if (sweep > 2 * uiPi)
|
||||
sweep = 2 * uiPi;
|
||||
cw = false;
|
||||
if (negative)
|
||||
cw = true;
|
||||
CGPathAddArc(p->path, NULL,
|
||||
xCenter, yCenter,
|
||||
radius,
|
||||
startAngle, startAngle + sweep,
|
||||
cw);
|
||||
}
|
||||
|
||||
void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
|
||||
{
|
||||
// TODO likewise
|
||||
if (p->ended)
|
||||
uiprivImplBug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
|
||||
CGPathAddCurveToPoint(p->path, NULL,
|
||||
c1x, c1y,
|
||||
c2x, c2y,
|
||||
endX, endY);
|
||||
}
|
||||
|
||||
void uiDrawPathCloseFigure(uiDrawPath *p)
|
||||
{
|
||||
// TODO likewise
|
||||
if (p->ended)
|
||||
uiprivImplBug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
|
||||
CGPathCloseSubpath(p->path);
|
||||
}
|
||||
|
||||
void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
|
||||
{
|
||||
if (p->ended)
|
||||
uiprivUserBug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
|
||||
CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
|
||||
}
|
||||
|
||||
void uiDrawPathEnd(uiDrawPath *p)
|
||||
{
|
||||
p->ended = TRUE;
|
||||
}
|
||||
|
||||
uiDrawContext *uiprivDrawNewContext(CGContextRef ctxt, CGFloat height)
|
||||
{
|
||||
uiDrawContext *c;
|
||||
|
||||
c = uiprivNew(uiDrawContext);
|
||||
c->c = ctxt;
|
||||
c->height = height;
|
||||
return c;
|
||||
}
|
||||
|
||||
void uiprivDrawFreeContext(uiDrawContext *c)
|
||||
{
|
||||
uiprivFree(c);
|
||||
}
|
||||
|
||||
// a stroke is identical to a fill of a stroked path
|
||||
// we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572
|
||||
// doing this for other brushes works too
|
||||
void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
|
||||
{
|
||||
CGLineCap cap;
|
||||
CGLineJoin join;
|
||||
CGPathRef dashPath;
|
||||
CGFloat *dashes;
|
||||
size_t i;
|
||||
uiDrawPath p2;
|
||||
|
||||
if (!path->ended)
|
||||
uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
|
||||
|
||||
switch (p->Cap) {
|
||||
case uiDrawLineCapFlat:
|
||||
cap = kCGLineCapButt;
|
||||
break;
|
||||
case uiDrawLineCapRound:
|
||||
cap = kCGLineCapRound;
|
||||
break;
|
||||
case uiDrawLineCapSquare:
|
||||
cap = kCGLineCapSquare;
|
||||
break;
|
||||
}
|
||||
switch (p->Join) {
|
||||
case uiDrawLineJoinMiter:
|
||||
join = kCGLineJoinMiter;
|
||||
break;
|
||||
case uiDrawLineJoinRound:
|
||||
join = kCGLineJoinRound;
|
||||
break;
|
||||
case uiDrawLineJoinBevel:
|
||||
join = kCGLineJoinBevel;
|
||||
break;
|
||||
}
|
||||
|
||||
// create a temporary path identical to the previous one
|
||||
dashPath = (CGPathRef) path->path;
|
||||
if (p->NumDashes != 0) {
|
||||
dashes = (CGFloat *) uiprivAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
|
||||
for (i = 0; i < p->NumDashes; i++)
|
||||
dashes[i] = p->Dashes[i];
|
||||
dashPath = CGPathCreateCopyByDashingPath(path->path,
|
||||
NULL,
|
||||
p->DashPhase,
|
||||
dashes,
|
||||
p->NumDashes);
|
||||
uiprivFree(dashes);
|
||||
}
|
||||
// the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
|
||||
// the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
|
||||
p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,
|
||||
NULL,
|
||||
p->Thickness,
|
||||
cap,
|
||||
join,
|
||||
p->MiterLimit);
|
||||
if (p->NumDashes != 0)
|
||||
CGPathRelease(dashPath);
|
||||
|
||||
// always draw stroke fills using the winding rule
|
||||
// otherwise intersecting figures won't draw correctly
|
||||
p2.fillMode = uiDrawFillModeWinding;
|
||||
p2.ended = path->ended;
|
||||
uiDrawFill(c, &p2, b);
|
||||
// and clean up
|
||||
CGPathRelease((CGPathRef) (p2.path));
|
||||
}
|
||||
|
||||
// for a solid fill, we can merely have Core Graphics fill directly
|
||||
static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
||||
{
|
||||
// TODO this uses DeviceRGB; switch to sRGB
|
||||
CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
|
||||
switch (p->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextFillPath(ctxt);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOFillPath(ctxt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// for a gradient fill, we need to clip to the path and then draw the gradient
|
||||
// see http://stackoverflow.com/a/25034854/3408572
|
||||
static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
|
||||
{
|
||||
CGGradientRef gradient;
|
||||
CGColorSpaceRef colorspace;
|
||||
CGFloat *colors;
|
||||
CGFloat *locations;
|
||||
size_t i;
|
||||
|
||||
// gradients need a color space
|
||||
// for consistency with windows, use sRGB
|
||||
colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
|
||||
if (colorspace == NULL) {
|
||||
// TODO
|
||||
}
|
||||
// TODO add NULL check to other uses of CGColorSpace
|
||||
|
||||
// make the gradient
|
||||
colors = uiprivAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
|
||||
locations = uiprivAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
|
||||
for (i = 0; i < b->NumStops; i++) {
|
||||
colors[i * 4 + 0] = b->Stops[i].R;
|
||||
colors[i * 4 + 1] = b->Stops[i].G;
|
||||
colors[i * 4 + 2] = b->Stops[i].B;
|
||||
colors[i * 4 + 3] = b->Stops[i].A;
|
||||
locations[i] = b->Stops[i].Pos;
|
||||
}
|
||||
gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
|
||||
uiprivFree(locations);
|
||||
uiprivFree(colors);
|
||||
|
||||
// because we're mucking with clipping, we need to save the graphics state and restore it later
|
||||
CGContextSaveGState(ctxt);
|
||||
|
||||
// clip
|
||||
switch (p->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextClip(ctxt);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOClip(ctxt);
|
||||
break;
|
||||
}
|
||||
|
||||
// draw the gradient
|
||||
switch (b->Type) {
|
||||
case uiDrawBrushTypeLinearGradient:
|
||||
CGContextDrawLinearGradient(ctxt,
|
||||
gradient,
|
||||
CGPointMake(b->X0, b->Y0),
|
||||
CGPointMake(b->X1, b->Y1),
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
break;
|
||||
case uiDrawBrushTypeRadialGradient:
|
||||
CGContextDrawRadialGradient(ctxt,
|
||||
gradient,
|
||||
CGPointMake(b->X0, b->Y0),
|
||||
// make the start circle radius 0 to make it a point
|
||||
0,
|
||||
CGPointMake(b->X1, b->Y1),
|
||||
b->OuterRadius,
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
|
||||
break;
|
||||
}
|
||||
|
||||
// and clean up
|
||||
CGContextRestoreGState(ctxt);
|
||||
CGGradientRelease(gradient);
|
||||
CGColorSpaceRelease(colorspace);
|
||||
}
|
||||
|
||||
void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
|
||||
{
|
||||
if (!path->ended)
|
||||
uiprivUserBug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
|
||||
CGContextAddPath(c->c, (CGPathRef) (path->path));
|
||||
switch (b->Type) {
|
||||
case uiDrawBrushTypeSolid:
|
||||
fillSolid(c->c, path, b);
|
||||
return;
|
||||
case uiDrawBrushTypeLinearGradient:
|
||||
case uiDrawBrushTypeRadialGradient:
|
||||
fillGradient(c->c, path, b);
|
||||
return;
|
||||
// case uiDrawBrushTypeImage:
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
uiprivUserBug("Unknown brush type %d passed to uiDrawFill().", b->Type);
|
||||
}
|
||||
|
||||
static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
|
||||
{
|
||||
c->a = m->M11;
|
||||
c->b = m->M12;
|
||||
c->c = m->M21;
|
||||
c->d = m->M22;
|
||||
c->tx = m->M31;
|
||||
c->ty = m->M32;
|
||||
}
|
||||
|
||||
static void c2m(CGAffineTransform *c, uiDrawMatrix *m)
|
||||
{
|
||||
m->M11 = c->a;
|
||||
m->M12 = c->b;
|
||||
m->M21 = c->c;
|
||||
m->M22 = c->d;
|
||||
m->M31 = c->tx;
|
||||
m->M32 = c->ty;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
|
||||
m2c(m, &c);
|
||||
c = CGAffineTransformTranslate(c, x, y);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
double xt, yt;
|
||||
|
||||
m2c(m, &c);
|
||||
xt = x;
|
||||
yt = y;
|
||||
uiprivScaleCenter(xCenter, yCenter, &xt, &yt);
|
||||
c = CGAffineTransformTranslate(c, xt, yt);
|
||||
c = CGAffineTransformScale(c, x, y);
|
||||
c = CGAffineTransformTranslate(c, -xt, -yt);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
|
||||
m2c(m, &c);
|
||||
c = CGAffineTransformTranslate(c, x, y);
|
||||
c = CGAffineTransformRotate(c, amount);
|
||||
c = CGAffineTransformTranslate(c, -x, -y);
|
||||
c2m(&c, m);
|
||||
}
|
||||
|
||||
void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
|
||||
{
|
||||
uiprivFallbackSkew(m, x, y, xamount, yamount);
|
||||
}
|
||||
|
||||
void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
CGAffineTransform d;
|
||||
|
||||
m2c(dest, &c);
|
||||
m2c(src, &d);
|
||||
c = CGAffineTransformConcat(c, d);
|
||||
c2m(&c, dest);
|
||||
}
|
||||
|
||||
// there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible
|
||||
// therefore, special care must be taken to catch matrices who are their own inverses
|
||||
// TODO figure out which matrices these are and do so
|
||||
int uiDrawMatrixInvertible(uiDrawMatrix *m)
|
||||
{
|
||||
CGAffineTransform c, d;
|
||||
|
||||
m2c(m, &c);
|
||||
d = CGAffineTransformInvert(c);
|
||||
return CGAffineTransformEqualToTransform(c, d) == false;
|
||||
}
|
||||
|
||||
int uiDrawMatrixInvert(uiDrawMatrix *m)
|
||||
{
|
||||
CGAffineTransform c, d;
|
||||
|
||||
m2c(m, &c);
|
||||
d = CGAffineTransformInvert(c);
|
||||
if (CGAffineTransformEqualToTransform(c, d))
|
||||
return 0;
|
||||
c2m(&d, m);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
CGPoint p;
|
||||
|
||||
m2c(m, &c);
|
||||
p = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);
|
||||
*x = p.x;
|
||||
*y = p.y;
|
||||
}
|
||||
|
||||
void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
|
||||
{
|
||||
CGAffineTransform c;
|
||||
CGSize s;
|
||||
|
||||
m2c(m, &c);
|
||||
s = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);
|
||||
*x = s.width;
|
||||
*y = s.height;
|
||||
}
|
||||
|
||||
void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
|
||||
{
|
||||
CGAffineTransform cm;
|
||||
|
||||
m2c(m, &cm);
|
||||
CGContextConcatCTM(c->c, cm);
|
||||
}
|
||||
|
||||
void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
|
||||
{
|
||||
if (!path->ended)
|
||||
uiprivUserBug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
|
||||
CGContextAddPath(c->c, (CGPathRef) (path->path));
|
||||
switch (path->fillMode) {
|
||||
case uiDrawFillModeWinding:
|
||||
CGContextClip(c->c);
|
||||
break;
|
||||
case uiDrawFillModeAlternate:
|
||||
CGContextEOClip(c->c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO figure out what besides transforms these save/restore on all platforms
|
||||
void uiDrawSave(uiDrawContext *c)
|
||||
{
|
||||
CGContextSaveGState(c->c);
|
||||
}
|
||||
|
||||
void uiDrawRestore(uiDrawContext *c)
|
||||
{
|
||||
CGContextRestoreGState(c->c);
|
||||
}
|
||||
214
dep/libui/darwin/drawtext.m
Normal file
214
dep/libui/darwin/drawtext.m
Normal file
@@ -0,0 +1,214 @@
|
||||
// 7 march 2018
|
||||
#import "uipriv_darwin.h"
|
||||
#import "draw.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// problem: for a CTFrame made from an empty string, the CTLine array will be empty, and we will crash when doing anything requiring a CTLine
|
||||
// solution: for those cases, maintain a separate framesetter just for computing those things
|
||||
// in the usual case, the separate copy will just be identical to the regular one, with extra references to everything within
|
||||
@interface uiprivTextFrame : NSObject {
|
||||
CFAttributedStringRef attrstr;
|
||||
NSArray *backgroundParams;
|
||||
CTFramesetterRef framesetter;
|
||||
CGSize size;
|
||||
CGPathRef path;
|
||||
CTFrameRef frame;
|
||||
}
|
||||
- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p;
|
||||
- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y;
|
||||
- (void)returnWidth:(double *)width height:(double *)height;
|
||||
- (CFArrayRef)lines;
|
||||
@end
|
||||
|
||||
@implementation uiprivDrawTextBackgroundParams
|
||||
|
||||
- (id)initWithStart:(size_t)s end:(size_t)e r:(double)red g:(double)green b:(double)blue a:(double)alpha
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->start = s;
|
||||
self->end = e;
|
||||
self->r = red;
|
||||
self->g = green;
|
||||
self->b = blue;
|
||||
self->a = alpha;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)draw:(CGContextRef)c layout:(uiDrawTextLayout *)layout at:(double)x y:(double)y utf8Mapping:(const size_t *)u16tou8
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation uiprivTextFrame
|
||||
|
||||
- (id)initWithLayoutParams:(uiDrawTextLayoutParams *)p
|
||||
{
|
||||
CFRange range;
|
||||
CGFloat cgwidth;
|
||||
CFRange unused;
|
||||
CGRect rect;
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->attrstr = uiprivAttributedStringToCFAttributedString(p, &(self->backgroundParams));
|
||||
// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
|
||||
self->framesetter = CTFramesetterCreateWithAttributedString(self->attrstr);
|
||||
if (self->framesetter == NULL) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
range.location = 0;
|
||||
range.length = CFAttributedStringGetLength(self->attrstr);
|
||||
|
||||
cgwidth = (CGFloat) (p->Width);
|
||||
if (cgwidth < 0)
|
||||
cgwidth = CGFLOAT_MAX;
|
||||
self->size = CTFramesetterSuggestFrameSizeWithConstraints(self->framesetter,
|
||||
range,
|
||||
// TODO kCTFramePathWidthAttributeName?
|
||||
NULL,
|
||||
CGSizeMake(cgwidth, CGFLOAT_MAX),
|
||||
&unused); // not documented as accepting NULL (TODO really?)
|
||||
|
||||
rect.origin = CGPointZero;
|
||||
rect.size = self->size;
|
||||
self->path = CGPathCreateWithRect(rect, NULL);
|
||||
self->frame = CTFramesetterCreateFrame(self->framesetter,
|
||||
range,
|
||||
self->path,
|
||||
// TODO kCTFramePathWidthAttributeName?
|
||||
NULL);
|
||||
if (self->frame == NULL) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CFRelease(self->frame);
|
||||
CFRelease(self->path);
|
||||
CFRelease(self->framesetter);
|
||||
[self->backgroundParams release];
|
||||
CFRelease(self->attrstr);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)draw:(uiDrawContext *)c textLayout:(uiDrawTextLayout *)tl at:(double)x y:(double)y
|
||||
{
|
||||
uiprivDrawTextBackgroundParams *dtb;
|
||||
CGAffineTransform textMatrix;
|
||||
|
||||
CGContextSaveGState(c->c);
|
||||
// save the text matrix because it's not part of the graphics state
|
||||
textMatrix = CGContextGetTextMatrix(c->c);
|
||||
|
||||
for (dtb in self->backgroundParams)
|
||||
/* TODO */;
|
||||
|
||||
// Core Text doesn't draw onto a flipped view correctly; we have to pretend it was unflipped
|
||||
// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
|
||||
// TODO how is this affected by a non-identity CTM?
|
||||
CGContextTranslateCTM(c->c, 0, c->height);
|
||||
CGContextScaleCTM(c->c, 1.0, -1.0);
|
||||
CGContextSetTextMatrix(c->c, CGAffineTransformIdentity);
|
||||
|
||||
// wait, that's not enough; we need to offset y values to account for our new flipping
|
||||
// TODO explain this calculation
|
||||
y = c->height - self->size.height - y;
|
||||
|
||||
// CTFrameDraw() draws in the path we specified when creating the frame
|
||||
// this means that in our usage, CTFrameDraw() will draw at (0,0)
|
||||
// so move the origin to be at (x,y) instead
|
||||
// TODO are the signs correct?
|
||||
CGContextTranslateCTM(c->c, x, y);
|
||||
|
||||
CTFrameDraw(self->frame, c->c);
|
||||
|
||||
CGContextSetTextMatrix(c->c, textMatrix);
|
||||
CGContextRestoreGState(c->c);
|
||||
}
|
||||
|
||||
- (void)returnWidth:(double *)width height:(double *)height
|
||||
{
|
||||
if (width != NULL)
|
||||
*width = self->size.width;
|
||||
if (height != NULL)
|
||||
*height = self->size.height;
|
||||
}
|
||||
|
||||
- (CFArrayRef)lines
|
||||
{
|
||||
return CTFrameGetLines(self->frame);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiDrawTextLayout {
|
||||
uiprivTextFrame *frame;
|
||||
uiprivTextFrame *forLines;
|
||||
BOOL empty;
|
||||
|
||||
// for converting CFAttributedString indices from/to byte offsets
|
||||
size_t *u8tou16;
|
||||
size_t nUTF8;
|
||||
size_t *u16tou8;
|
||||
size_t nUTF16;
|
||||
};
|
||||
|
||||
uiDrawTextLayout *uiDrawNewTextLayout(uiDrawTextLayoutParams *p)
|
||||
{
|
||||
uiDrawTextLayout *tl;
|
||||
|
||||
tl = uiprivNew(uiDrawTextLayout);
|
||||
tl->frame = [[uiprivTextFrame alloc] initWithLayoutParams:p];
|
||||
if (uiAttributedStringLen(p->String) != 0)
|
||||
tl->forLines = [tl->frame retain];
|
||||
else {
|
||||
uiAttributedString *space;
|
||||
uiDrawTextLayoutParams p2;
|
||||
|
||||
tl->empty = YES;
|
||||
space = uiNewAttributedString(" ");
|
||||
p2 = *p;
|
||||
p2.String = space;
|
||||
tl->forLines = [[uiprivTextFrame alloc] initWithLayoutParams:&p2];
|
||||
uiFreeAttributedString(space);
|
||||
}
|
||||
|
||||
// and finally copy the UTF-8/UTF-16 conversion tables
|
||||
tl->u8tou16 = uiprivAttributedStringCopyUTF8ToUTF16Table(p->String, &(tl->nUTF8));
|
||||
tl->u16tou8 = uiprivAttributedStringCopyUTF16ToUTF8Table(p->String, &(tl->nUTF16));
|
||||
return tl;
|
||||
}
|
||||
|
||||
void uiDrawFreeTextLayout(uiDrawTextLayout *tl)
|
||||
{
|
||||
uiprivFree(tl->u16tou8);
|
||||
uiprivFree(tl->u8tou16);
|
||||
[tl->forLines release];
|
||||
[tl->frame release];
|
||||
uiprivFree(tl);
|
||||
}
|
||||
|
||||
// TODO document that (x,y) is the top-left corner of the *entire frame*
|
||||
void uiDrawText(uiDrawContext *c, uiDrawTextLayout *tl, double x, double y)
|
||||
{
|
||||
[tl->frame draw:c textLayout:tl at:x y:y];
|
||||
}
|
||||
|
||||
// TODO document that the width and height of a layout is not necessarily the sum of the widths and heights of its constituent lines
|
||||
// TODO width doesn't include trailing whitespace...
|
||||
// TODO figure out how paragraph spacing should play into this
|
||||
// TODO standardize and document the behavior of this on an empty layout
|
||||
void uiDrawTextLayoutExtents(uiDrawTextLayout *tl, double *width, double *height)
|
||||
{
|
||||
// TODO explain this, given the above
|
||||
[tl->frame returnWidth:width height:NULL];
|
||||
[tl->forLines returnWidth:NULL height:height];
|
||||
}
|
||||
186
dep/libui/darwin/editablecombo.m
Normal file
186
dep/libui/darwin/editablecombo.m
Normal file
@@ -0,0 +1,186 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// So why did I split uiCombobox into uiCombobox and uiEditableCombobox? Here's (90% of the; the other 10% is GTK+ events) answer:
|
||||
// When you type a value into a NSComboBox that just happens to be in the list, it will autoselect that item!
|
||||
// I can't seem to find a workaround.
|
||||
// Fortunately, there's other weird behaviors that made this split worth it.
|
||||
// And besides, selected items make little sense with editable comboboxes... you either separate or combine them with the text entry :V
|
||||
|
||||
// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
#define comboboxWidth 96
|
||||
|
||||
@interface libui_intrinsicWidthNSComboBox : NSComboBox
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSComboBox
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = comboboxWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiEditableCombobox {
|
||||
uiDarwinControl c;
|
||||
NSComboBox *cb;
|
||||
void (*onChanged)(uiEditableCombobox *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@interface editableComboboxDelegateClass : NSObject<NSComboBoxDelegate> {
|
||||
uiprivMap *comboboxes;
|
||||
}
|
||||
- (void)controlTextDidChange:(NSNotification *)note;
|
||||
- (void)comboBoxSelectionDidChange:(NSNotification *)note;
|
||||
- (void)registerCombobox:(uiEditableCombobox *)c;
|
||||
- (void)unregisterCombobox:(uiEditableCombobox *)c;
|
||||
@end
|
||||
|
||||
@implementation editableComboboxDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->comboboxes = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->comboboxes);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
uiEditableCombobox *c;
|
||||
|
||||
// TODO normalize the cast styles in these calls
|
||||
c = uiEditableCombobox(uiprivMapGet(self->comboboxes, [note object]));
|
||||
(*(c->onChanged))(c, c->onChangedData);
|
||||
}
|
||||
|
||||
// the above doesn't handle when an item is selected; this will
|
||||
- (void)comboBoxSelectionDidChange:(NSNotification *)note
|
||||
{
|
||||
// except this is sent BEFORE the entry is changed, and that doesn't send the above, so
|
||||
// this is via http://stackoverflow.com/a/21059819/3408572 - it avoids the need to manage selected items
|
||||
// this still isn't perfect — I get residual changes to the same value while navigating the list — but it's good enough
|
||||
[self performSelector:@selector(controlTextDidChange:)
|
||||
withObject:note
|
||||
afterDelay:0];
|
||||
}
|
||||
|
||||
- (void)registerCombobox:(uiEditableCombobox *)c
|
||||
{
|
||||
uiprivMapSet(self->comboboxes, c->cb, c);
|
||||
[c->cb setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterCombobox:(uiEditableCombobox *)c
|
||||
{
|
||||
[c->cb setDelegate:nil];
|
||||
uiprivMapDelete(self->comboboxes, c->cb);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static editableComboboxDelegateClass *comboboxDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiEditableCombobox, cb)
|
||||
|
||||
static void uiEditableComboboxDestroy(uiControl *cc)
|
||||
{
|
||||
uiEditableCombobox *c = uiEditableCombobox(cc);
|
||||
|
||||
[comboboxDelegate unregisterCombobox:c];
|
||||
[c->cb release];
|
||||
uiFreeControl(uiControl(c));
|
||||
}
|
||||
|
||||
void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
|
||||
{
|
||||
[c->cb addItemWithObjectValue:uiprivToNSString(text)];
|
||||
}
|
||||
|
||||
char *uiEditableComboboxText(uiEditableCombobox *c)
|
||||
{
|
||||
return uiDarwinNSStringToText([c->cb stringValue]);
|
||||
}
|
||||
|
||||
void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
|
||||
{
|
||||
NSString *t;
|
||||
|
||||
t = uiprivToNSString(text);
|
||||
[c->cb setStringValue:t];
|
||||
// yes, let's imitate the behavior that caused uiEditableCombobox to be separate in the first place!
|
||||
// just to avoid confusion when users see an option in the list in the text field but not selected in the list
|
||||
[c->cb selectItemWithObjectValue:t];
|
||||
}
|
||||
|
||||
#if 0
|
||||
// LONGTERM
|
||||
void uiEditableComboboxSetSelected(uiEditableCombobox *c, int n)
|
||||
{
|
||||
if (c->editable) {
|
||||
// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256
|
||||
id delegate;
|
||||
|
||||
// this triggers the delegate; turn it off for now
|
||||
delegate = [c->cb delegate];
|
||||
[c->cb setDelegate:nil];
|
||||
|
||||
// this seems to work fine for -1 too
|
||||
[c->cb selectItemAtIndex:n];
|
||||
if (n == -1)
|
||||
[c->cb setObjectValue:@""];
|
||||
else
|
||||
[c->cb setObjectValue:[c->cb objectValueOfSelectedItem]];
|
||||
|
||||
[c->cb setDelegate:delegate];
|
||||
return;
|
||||
}
|
||||
[c->pb selectItemAtIndex:n];
|
||||
}
|
||||
#endif
|
||||
|
||||
void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
|
||||
{
|
||||
c->onChanged = f;
|
||||
c->onChangedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiEditableCombobox *c, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiEditableCombobox *uiNewEditableCombobox(void)
|
||||
{
|
||||
uiEditableCombobox *c;
|
||||
|
||||
uiDarwinNewControl(uiEditableCombobox, c);
|
||||
|
||||
c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect];
|
||||
[c->cb setUsesDataSource:NO];
|
||||
[c->cb setButtonBordered:YES];
|
||||
[c->cb setCompletes:NO];
|
||||
uiDarwinSetControlFont(c->cb, NSRegularControlSize);
|
||||
|
||||
if (comboboxDelegate == nil) {
|
||||
comboboxDelegate = [[editableComboboxDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:comboboxDelegate];
|
||||
}
|
||||
[comboboxDelegate registerCombobox:c];
|
||||
uiEditableComboboxOnChanged(c, defaultOnChanged, NULL);
|
||||
|
||||
return c;
|
||||
}
|
||||
251
dep/libui/darwin/entry.m
Normal file
251
dep/libui/darwin/entry.m
Normal file
@@ -0,0 +1,251 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// Text fields for entering text have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
#define textfieldWidth 96
|
||||
|
||||
@interface libui_intrinsicWidthNSTextField : NSTextField
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSTextField
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = textfieldWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// TODO does this have one on its own?
|
||||
@interface libui_intrinsicWidthNSSecureTextField : NSSecureTextField
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSSecureTextField
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = textfieldWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// TODO does this have one on its own?
|
||||
@interface libui_intrinsicWidthNSSearchField : NSSearchField
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSSearchField
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = textfieldWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiEntry {
|
||||
uiDarwinControl c;
|
||||
NSTextField *textfield;
|
||||
void (*onChanged)(uiEntry *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
static BOOL isSearchField(NSTextField *tf)
|
||||
{
|
||||
return [tf isKindOfClass:[NSSearchField class]];
|
||||
}
|
||||
|
||||
@interface entryDelegateClass : NSObject<NSTextFieldDelegate> {
|
||||
uiprivMap *entries;
|
||||
}
|
||||
- (void)controlTextDidChange:(NSNotification *)note;
|
||||
- (IBAction)onSearch:(id)sender;
|
||||
- (void)registerEntry:(uiEntry *)e;
|
||||
- (void)unregisterEntry:(uiEntry *)e;
|
||||
@end
|
||||
|
||||
@implementation entryDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->entries = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->entries);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
[self onSearch:[note object]];
|
||||
}
|
||||
|
||||
- (IBAction)onSearch:(id)sender
|
||||
{
|
||||
uiEntry *e;
|
||||
|
||||
e = (uiEntry *) uiprivMapGet(self->entries, sender);
|
||||
(*(e->onChanged))(e, e->onChangedData);
|
||||
}
|
||||
|
||||
- (void)registerEntry:(uiEntry *)e
|
||||
{
|
||||
uiprivMapSet(self->entries, e->textfield, e);
|
||||
if (isSearchField(e->textfield)) {
|
||||
[e->textfield setTarget:self];
|
||||
[e->textfield setAction:@selector(onSearch:)];
|
||||
} else
|
||||
[e->textfield setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterEntry:(uiEntry *)e
|
||||
{
|
||||
if (isSearchField(e->textfield))
|
||||
[e->textfield setTarget:nil];
|
||||
else
|
||||
[e->textfield setDelegate:nil];
|
||||
uiprivMapDelete(self->entries, e->textfield);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static entryDelegateClass *entryDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiEntry, textfield)
|
||||
|
||||
static void uiEntryDestroy(uiControl *c)
|
||||
{
|
||||
uiEntry *e = uiEntry(c);
|
||||
|
||||
[entryDelegate unregisterEntry:e];
|
||||
[e->textfield release];
|
||||
uiFreeControl(uiControl(e));
|
||||
}
|
||||
|
||||
char *uiEntryText(uiEntry *e)
|
||||
{
|
||||
return uiDarwinNSStringToText([e->textfield stringValue]);
|
||||
}
|
||||
|
||||
void uiEntrySetText(uiEntry *e, const char *text)
|
||||
{
|
||||
[e->textfield setStringValue:uiprivToNSString(text)];
|
||||
// don't queue the control for resize; entry sizes are independent of their contents
|
||||
}
|
||||
|
||||
void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)
|
||||
{
|
||||
e->onChanged = f;
|
||||
e->onChangedData = data;
|
||||
}
|
||||
|
||||
int uiEntryReadOnly(uiEntry *e)
|
||||
{
|
||||
return [e->textfield isEditable] == NO;
|
||||
}
|
||||
|
||||
void uiEntrySetReadOnly(uiEntry *e, int readonly)
|
||||
{
|
||||
BOOL editable;
|
||||
|
||||
editable = YES;
|
||||
if (readonly)
|
||||
editable = NO;
|
||||
[e->textfield setEditable:editable];
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiEntry *e, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/
|
||||
void uiprivFinishNewTextField(NSTextField *t, BOOL isEntry)
|
||||
{
|
||||
uiDarwinSetControlFont(t, NSRegularControlSize);
|
||||
|
||||
// THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR
|
||||
[t setBordered:NO];
|
||||
[t setBezelStyle:NSTextFieldSquareBezel];
|
||||
[t setBezeled:isEntry];
|
||||
|
||||
// we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details
|
||||
|
||||
[[t cell] setLineBreakMode:NSLineBreakByClipping];
|
||||
[[t cell] setScrollable:YES];
|
||||
}
|
||||
|
||||
static NSTextField *realNewEditableTextField(Class class)
|
||||
{
|
||||
NSTextField *tf;
|
||||
|
||||
tf = [[class alloc] initWithFrame:NSZeroRect];
|
||||
[tf setSelectable:YES]; // otherwise the setting is masked by the editable default of YES
|
||||
uiprivFinishNewTextField(tf, YES);
|
||||
return tf;
|
||||
}
|
||||
|
||||
NSTextField *uiprivNewEditableTextField(void)
|
||||
{
|
||||
return realNewEditableTextField([libui_intrinsicWidthNSTextField class]);
|
||||
}
|
||||
|
||||
static uiEntry *finishNewEntry(Class class)
|
||||
{
|
||||
uiEntry *e;
|
||||
|
||||
uiDarwinNewControl(uiEntry, e);
|
||||
|
||||
e->textfield = realNewEditableTextField(class);
|
||||
|
||||
if (entryDelegate == nil) {
|
||||
entryDelegate = [[entryDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:entryDelegate];
|
||||
}
|
||||
[entryDelegate registerEntry:e];
|
||||
uiEntryOnChanged(e, defaultOnChanged, NULL);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
uiEntry *uiNewEntry(void)
|
||||
{
|
||||
return finishNewEntry([libui_intrinsicWidthNSTextField class]);
|
||||
}
|
||||
|
||||
uiEntry *uiNewPasswordEntry(void)
|
||||
{
|
||||
return finishNewEntry([libui_intrinsicWidthNSSecureTextField class]);
|
||||
}
|
||||
|
||||
uiEntry *uiNewSearchEntry(void)
|
||||
{
|
||||
uiEntry *e;
|
||||
NSSearchField *s;
|
||||
|
||||
e = finishNewEntry([libui_intrinsicWidthNSSearchField class]);
|
||||
s = (NSSearchField *) (e->textfield);
|
||||
// TODO these are only on 10.10
|
||||
// [s setSendsSearchStringImmediately:NO];
|
||||
// [s setSendsWholeSearchString:NO];
|
||||
[s setBordered:NO];
|
||||
[s setBezelStyle:NSTextFieldRoundedBezel];
|
||||
[s setBezeled:YES];
|
||||
return e;
|
||||
}
|
||||
232
dep/libui/darwin/fontbutton.m
Normal file
232
dep/libui/darwin/fontbutton.m
Normal file
@@ -0,0 +1,232 @@
|
||||
// 14 april 2016
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
@interface uiprivFontButton : NSButton {
|
||||
uiFontButton *libui_b;
|
||||
NSFont *libui_font;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b;
|
||||
- (void)updateFontButtonLabel;
|
||||
- (IBAction)fontButtonClicked:(id)sender;
|
||||
- (void)activateFontButton;
|
||||
- (void)deactivateFontButton:(BOOL)activatingAnother;
|
||||
- (void)deactivateOnClose:(NSNotification *)note;
|
||||
- (void)getfontdesc:(uiFontDescriptor *)uidesc;
|
||||
@end
|
||||
|
||||
// only one may be active at one time
|
||||
static uiprivFontButton *activeFontButton = nil;
|
||||
|
||||
struct uiFontButton {
|
||||
uiDarwinControl c;
|
||||
uiprivFontButton *button;
|
||||
void (*onChanged)(uiFontButton *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@implementation uiprivFontButton
|
||||
|
||||
- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b
|
||||
{
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self->libui_b = b;
|
||||
|
||||
// imitate a NSColorWell in appearance
|
||||
[self setButtonType:NSPushOnPushOffButton];
|
||||
[self setBordered:YES];
|
||||
[self setBezelStyle:NSShadowlessSquareBezelStyle];
|
||||
|
||||
// default font values according to the CTFontDescriptor reference
|
||||
// this is autoreleased (thanks swillits in irc.freenode.net/#macdev)
|
||||
self->libui_font = [[NSFont fontWithName:@"Helvetica" size:12.0] retain];
|
||||
[self updateFontButtonLabel];
|
||||
|
||||
// for when clicked
|
||||
[self setTarget:self];
|
||||
[self setAction:@selector(fontButtonClicked:)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// clean up notifications
|
||||
if (activeFontButton == self)
|
||||
[self deactivateFontButton:NO];
|
||||
[self->libui_font release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)updateFontButtonLabel
|
||||
{
|
||||
NSString *title;
|
||||
|
||||
title = [NSString stringWithFormat:@"%@ %g",
|
||||
[self->libui_font displayName],
|
||||
[self->libui_font pointSize]];
|
||||
[self setTitle:title];
|
||||
}
|
||||
|
||||
- (IBAction)fontButtonClicked:(id)sender
|
||||
{
|
||||
if ([self state] == NSOnState)
|
||||
[self activateFontButton];
|
||||
else
|
||||
[self deactivateFontButton:NO];
|
||||
}
|
||||
|
||||
- (void)activateFontButton
|
||||
{
|
||||
NSFontManager *sfm;
|
||||
|
||||
sfm = [NSFontManager sharedFontManager];
|
||||
if (activeFontButton != nil)
|
||||
[activeFontButton deactivateFontButton:YES];
|
||||
[sfm setTarget:self];
|
||||
[sfm setSelectedFont:self->libui_font isMultiple:NO];
|
||||
[sfm orderFrontFontPanel:self];
|
||||
activeFontButton = self;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(deactivateOnClose:)
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSFontPanel sharedFontPanel]];
|
||||
[self setState:NSOnState];
|
||||
}
|
||||
|
||||
- (void)deactivateFontButton:(BOOL)activatingAnother
|
||||
{
|
||||
NSFontManager *sfm;
|
||||
|
||||
sfm = [NSFontManager sharedFontManager];
|
||||
[sfm setTarget:nil];
|
||||
if (!activatingAnother)
|
||||
[[NSFontPanel sharedFontPanel] orderOut:self];
|
||||
activeFontButton = nil;
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self
|
||||
name:NSWindowWillCloseNotification
|
||||
object:[NSFontPanel sharedFontPanel]];
|
||||
[self setState:NSOffState];
|
||||
}
|
||||
|
||||
- (void)deactivateOnClose:(NSNotification *)note
|
||||
{
|
||||
[self deactivateFontButton:NO];
|
||||
}
|
||||
|
||||
- (void)changeFont:(id)sender
|
||||
{
|
||||
NSFontManager *fm;
|
||||
NSFont *old;
|
||||
uiFontButton *b = self->libui_b;
|
||||
|
||||
fm = (NSFontManager *) sender;
|
||||
old = self->libui_font;
|
||||
self->libui_font = [sender convertFont:self->libui_font];
|
||||
// do this even if it returns the same; we don't own anything that isn't from a new or alloc/init
|
||||
[self->libui_font retain];
|
||||
// do this second just in case
|
||||
[old release];
|
||||
[self updateFontButtonLabel];
|
||||
(*(b->onChanged))(b, b->onChangedData);
|
||||
}
|
||||
|
||||
- (NSUInteger)validModesForFontPanel:(NSFontPanel *)panel
|
||||
{
|
||||
return NSFontPanelFaceModeMask |
|
||||
NSFontPanelSizeModeMask |
|
||||
NSFontPanelCollectionModeMask;
|
||||
}
|
||||
|
||||
- (void)getfontdesc:(uiFontDescriptor *)uidesc
|
||||
{
|
||||
CTFontRef ctfont;
|
||||
CTFontDescriptorRef ctdesc;
|
||||
|
||||
ctfont = (CTFontRef) (self->libui_font);
|
||||
ctdesc = CTFontCopyFontDescriptor(ctfont);
|
||||
uiprivFontDescriptorFromCTFontDescriptor(ctdesc, uidesc);
|
||||
CFRelease(ctdesc);
|
||||
uidesc->Size = CTFontGetSize(ctfont);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaults(uiFontButton, button)
|
||||
|
||||
// we do not want font change events to be sent to any controls other than the font buttons
|
||||
// see main.m for more details
|
||||
BOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to)
|
||||
{
|
||||
if (sel != @selector(changeFont:))
|
||||
return NO;
|
||||
return ![to isKindOfClass:[uiprivFontButton class]];
|
||||
}
|
||||
|
||||
// we do not want NSFontPanelValidation messages to be sent to any controls other than the font buttons when a font button is active
|
||||
// see main.m for more details
|
||||
BOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
|
||||
{
|
||||
if (activeFontButton == nil)
|
||||
return NO;
|
||||
if (sel != @selector(validModesForFontPanel:))
|
||||
return NO;
|
||||
*override = activeFontButton;
|
||||
return YES;
|
||||
}
|
||||
|
||||
// we also don't want the panel to be usable when there's a dialog running; see stddialogs.m for more details on that
|
||||
// unfortunately the panel seems to ignore -setWorksWhenModal: so we'll have to do things ourselves
|
||||
@interface uiprivNonModalFontPanel : NSFontPanel
|
||||
@end
|
||||
|
||||
@implementation uiprivNonModalFontPanel
|
||||
|
||||
- (BOOL)worksWhenModal
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void uiprivSetupFontPanel(void)
|
||||
{
|
||||
[NSFontManager setFontPanelFactory:[uiprivNonModalFontPanel class]];
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiFontButton *b, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiFontButtonFont(uiFontButton *b, uiFontDescriptor *desc)
|
||||
{
|
||||
[b->button getfontdesc:desc];
|
||||
}
|
||||
|
||||
void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
|
||||
{
|
||||
b->onChanged = f;
|
||||
b->onChangedData = data;
|
||||
}
|
||||
|
||||
uiFontButton *uiNewFontButton(void)
|
||||
{
|
||||
uiFontButton *b;
|
||||
|
||||
uiDarwinNewControl(uiFontButton, b);
|
||||
|
||||
b->button = [[uiprivFontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b];
|
||||
uiDarwinSetControlFont(b->button, NSRegularControlSize);
|
||||
|
||||
uiFontButtonOnChanged(b, defaultOnChanged, NULL);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
void uiFreeFontButtonFont(uiFontDescriptor *desc)
|
||||
{
|
||||
// TODO ensure this is synchronized with fontmatch.m
|
||||
uiFreeText((char *) (desc->Family));
|
||||
}
|
||||
494
dep/libui/darwin/fontmatch.m
Normal file
494
dep/libui/darwin/fontmatch.m
Normal file
@@ -0,0 +1,494 @@
|
||||
// 3 january 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// TODOs:
|
||||
// - switching from Skia to a non-fvar-based font crashes because the CTFontDescriptorRef we get has an empty variation dictionary for some reason...
|
||||
// - Futura causes the Courier New in the drawtext example to be bold for some reason...
|
||||
|
||||
// Core Text exposes font style info in two forms:
|
||||
// - Fonts with a QuickDraw GX font variation (fvar) table, a feature
|
||||
// adopted by OpenType, expose variations directly.
|
||||
// - All other fonts have Core Text normalize the font style info
|
||||
// into a traits dictionary.
|
||||
// Of course this setup doesn't come without its hiccups and
|
||||
// glitches. In particular, not only are the exact rules not well
|
||||
// defined, but also font matching doesn't work as we want it to
|
||||
// (exactly how varies based on the way the style info is exposed).
|
||||
// So we'll have to implement style matching ourselves.
|
||||
// We can use Core Text's matching to get a complete list of
|
||||
// *possible* matches, and then we can filter out the ones we don't
|
||||
// want ourselves.
|
||||
//
|
||||
// To make things easier for us, we'll match by converting Core
|
||||
// Text's values back into libui values. This allows us to also use the
|
||||
// normalization code for filling in uiFontDescriptors from
|
||||
// Core Text fonts and font descriptors.
|
||||
//
|
||||
// Style matching needs to be done early in the font loading process;
|
||||
// in particular, we have to do this before adding any features,
|
||||
// because the descriptors returned by Core Text's own font
|
||||
// matching won't have any.
|
||||
|
||||
@implementation uiprivFontStyleData
|
||||
|
||||
- (id)initWithFont:(CTFontRef)f
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->font = f;
|
||||
CFRetain(self->font);
|
||||
self->desc = CTFontCopyFontDescriptor(self->font);
|
||||
if (![self prepare]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initWithDescriptor:(CTFontDescriptorRef)d
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->font = NULL;
|
||||
self->desc = d;
|
||||
CFRetain(self->desc);
|
||||
if (![self prepare]) {
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
#define REL(x) if (x != NULL) { CFRelease(x); x = NULL; }
|
||||
REL(self->variationAxes);
|
||||
REL(self->familyName);
|
||||
REL(self->preferredFamilyName);
|
||||
REL(self->fullName);
|
||||
REL(self->subFamilyName);
|
||||
REL(self->preferredSubFamilyName);
|
||||
REL(self->postScriptName);
|
||||
REL(self->variation);
|
||||
REL(self->styleName);
|
||||
REL(self->traits);
|
||||
CFRelease(self->desc);
|
||||
REL(self->font);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)prepare
|
||||
{
|
||||
CFNumberRef num;
|
||||
Boolean success;
|
||||
|
||||
self->traits = NULL;
|
||||
self->symbolic = 0;
|
||||
self->weight = 0;
|
||||
self->width = 0;
|
||||
self->didStyleName = NO;
|
||||
self->styleName = NULL;
|
||||
self->didVariation = NO;
|
||||
self->variation = NULL;
|
||||
self->hasRegistrationScope = NO;
|
||||
self->registrationScope = 0;
|
||||
self->didPostScriptName = NO;
|
||||
self->postScriptName = NULL;
|
||||
self->fontFormat = 0;
|
||||
self->didPreferredSubFamilyName = NO;
|
||||
self->preferredSubFamilyName = NULL;
|
||||
self->didSubFamilyName = NO;
|
||||
self->subFamilyName = NULL;
|
||||
self->didFullName = NO;
|
||||
self->fullName = NULL;
|
||||
self->didPreferredFamilyName = NO;
|
||||
self->preferredFamilyName = NULL;
|
||||
self->didFamilyName = NO;
|
||||
self->familyName = NULL;
|
||||
self->didVariationAxes = NO;
|
||||
self->variationAxes = NULL;
|
||||
|
||||
self->traits = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontTraitsAttribute);
|
||||
if (self->traits == NULL)
|
||||
return NO;
|
||||
|
||||
num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontSymbolicTrait);
|
||||
if (num == NULL)
|
||||
return NO;
|
||||
if (CFNumberGetValue(num, kCFNumberSInt32Type, &(self->symbolic)) == false)
|
||||
return NO;
|
||||
|
||||
num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWeightTrait);
|
||||
if (num == NULL)
|
||||
return NO;
|
||||
if (CFNumberGetValue(num, kCFNumberDoubleType, &(self->weight)) == false)
|
||||
return NO;
|
||||
|
||||
num = (CFNumberRef) CFDictionaryGetValue(self->traits, kCTFontWidthTrait);
|
||||
if (num == NULL)
|
||||
return NO;
|
||||
if (CFNumberGetValue(num, kCFNumberDoubleType, &(self->width)) == false)
|
||||
return NO;
|
||||
|
||||
// do these now for the sake of error checking
|
||||
num = (CFNumberRef) CTFontDescriptorCopyAttribute(desc, kCTFontRegistrationScopeAttribute);
|
||||
self->hasRegistrationScope = num != NULL;
|
||||
if (self->hasRegistrationScope) {
|
||||
success = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->registrationScope));
|
||||
CFRelease(num);
|
||||
if (success == false)
|
||||
return NO;
|
||||
}
|
||||
|
||||
num = (CFNumberRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontFormatAttribute);
|
||||
if (num == NULL)
|
||||
return NO;
|
||||
success = CFNumberGetValue(num, kCFNumberSInt32Type, &(self->fontFormat));
|
||||
CFRelease(num);
|
||||
if (success == false)
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)ensureFont
|
||||
{
|
||||
if (self->font != NULL)
|
||||
return;
|
||||
self->font = CTFontCreateWithFontDescriptor(self->desc, 0.0, NULL);
|
||||
}
|
||||
|
||||
- (CTFontSymbolicTraits)symbolicTraits
|
||||
{
|
||||
return self->symbolic;
|
||||
}
|
||||
|
||||
- (double)weight
|
||||
{
|
||||
return self->weight;
|
||||
}
|
||||
|
||||
- (double)width
|
||||
{
|
||||
return self->width;
|
||||
}
|
||||
|
||||
- (CFStringRef)styleName
|
||||
{
|
||||
if (!self->didStyleName) {
|
||||
self->didStyleName = YES;
|
||||
self->styleName = (CFStringRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontStyleNameAttribute);
|
||||
// The code we use this for (guessItalicOblique() below) checks if this is NULL or not, so we're good.
|
||||
}
|
||||
return self->styleName;
|
||||
}
|
||||
|
||||
- (CFDictionaryRef)variation
|
||||
{
|
||||
if (!self->didVariation) {
|
||||
self->didVariation = YES;
|
||||
self->variation = (CFDictionaryRef) CTFontDescriptorCopyAttribute(self->desc, kCTFontVariationAttribute);
|
||||
// This being NULL is used to determine whether a font uses variations at all, so we don't need to worry now.
|
||||
}
|
||||
return self->variation;
|
||||
}
|
||||
|
||||
- (BOOL)hasRegistrationScope
|
||||
{
|
||||
return self->hasRegistrationScope;
|
||||
}
|
||||
|
||||
- (CTFontManagerScope)registrationScope
|
||||
{
|
||||
return self->registrationScope;
|
||||
}
|
||||
|
||||
- (CFStringRef)postScriptName
|
||||
{
|
||||
if (!self->didPostScriptName) {
|
||||
self->didPostScriptName = YES;
|
||||
[self ensureFont];
|
||||
self->postScriptName = CTFontCopyPostScriptName(self->font);
|
||||
}
|
||||
return self->postScriptName;
|
||||
}
|
||||
|
||||
- (CFDataRef)table:(CTFontTableTag)tag
|
||||
{
|
||||
[self ensureFont];
|
||||
return CTFontCopyTable(self->font, tag, kCTFontTableOptionNoOptions);
|
||||
}
|
||||
|
||||
- (CTFontFormat)fontFormat
|
||||
{
|
||||
return self->fontFormat;
|
||||
}
|
||||
|
||||
// We don't need to worry if this or any of the functions that use it return NULL, because the code that uses it (libFontRegistry.dylib bug workarounds in fonttraits.m) checks for NULL.
|
||||
- (CFStringRef)fontName:(CFStringRef)key
|
||||
{
|
||||
[self ensureFont];
|
||||
return CTFontCopyName(self->font, key);
|
||||
}
|
||||
|
||||
#define FONTNAME(sel, did, var, key) \
|
||||
- (CFStringRef)sel \
|
||||
{ \
|
||||
if (!did) { \
|
||||
did = YES; \
|
||||
var = [self fontName:key]; \
|
||||
} \
|
||||
return var; \
|
||||
}
|
||||
FONTNAME(preferredSubFamilyName,
|
||||
self->didPreferredSubFamilyName,
|
||||
self->preferredSubFamilyName,
|
||||
uiprivUNDOC_kCTFontPreferredSubFamilyNameKey)
|
||||
FONTNAME(subFamilyName,
|
||||
self->didSubFamilyName,
|
||||
self->subFamilyName,
|
||||
kCTFontSubFamilyNameKey)
|
||||
FONTNAME(fullName,
|
||||
self->didFullName,
|
||||
self->fullName,
|
||||
kCTFontFullNameKey)
|
||||
FONTNAME(preferredFamilyName,
|
||||
self->didPreferredFamilyName,
|
||||
self->preferredFamilyName,
|
||||
uiprivUNDOC_kCTFontPreferredFamilyNameKey)
|
||||
FONTNAME(familyName,
|
||||
self->didFamilyName,
|
||||
self->familyName,
|
||||
kCTFontFamilyNameKey)
|
||||
|
||||
- (CFArrayRef)variationAxes
|
||||
{
|
||||
if (!self->didVariationAxes) {
|
||||
self->didVariationAxes = YES;
|
||||
[self ensureFont];
|
||||
self->variationAxes = CTFontCopyVariationAxes(self->font);
|
||||
// We don't care about the return value because we call this only on fonts that we know have variations anyway.
|
||||
}
|
||||
return self->variationAxes;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct closeness {
|
||||
CFIndex index;
|
||||
uiTextWeight weight;
|
||||
double italic;
|
||||
uiTextStretch stretch;
|
||||
double distance;
|
||||
};
|
||||
|
||||
// remember that in closeness, 0 means exact
|
||||
// in this case, since we define the range, we use 0.5 to mean "close enough" (oblique for italic and italic for oblique) and 1 to mean "not a match"
|
||||
static const double italicClosenessNormal[] = { 0, 1, 1 };
|
||||
static const double italicClosenessOblique[] = { 1, 0, 0.5 };
|
||||
static const double italicClosenessItalic[] = { 1, 0.5, 0 };
|
||||
static const double *italicClosenesses[] = {
|
||||
[uiTextItalicNormal] = italicClosenessNormal,
|
||||
[uiTextItalicOblique] = italicClosenessOblique,
|
||||
[uiTextItalicItalic] = italicClosenessItalic,
|
||||
};
|
||||
|
||||
// Core Text doesn't seem to differentiate between Italic and Oblique.
|
||||
// Pango's Core Text code just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
|
||||
static uiTextItalic guessItalicOblique(uiprivFontStyleData *d)
|
||||
{
|
||||
CFStringRef styleName;
|
||||
BOOL isOblique;
|
||||
|
||||
isOblique = NO; // default value
|
||||
styleName = [d styleName];
|
||||
if (styleName != NULL) {
|
||||
CFRange range;
|
||||
|
||||
range = CFStringFind(styleName, CFSTR("Oblique"), kCFCompareBackwards);
|
||||
if (range.location != kCFNotFound)
|
||||
isOblique = YES;
|
||||
}
|
||||
if (isOblique)
|
||||
return uiTextItalicOblique;
|
||||
return uiTextItalicItalic;
|
||||
}
|
||||
|
||||
// Italics are hard because Core Text does NOT distinguish between italic and oblique.
|
||||
// All Core Text provides is a slant value and the italic bit of the symbolic traits mask.
|
||||
// However, Core Text does seem to guarantee (from experimentation; see below) that the slant will be nonzero if and only if the italic bit is set, so we don't need to use the slant value.
|
||||
// Core Text also seems to guarantee that if a font lists itself as Italic or Oblique by name (font subfamily name, font style name, whatever), it will also have that bit set, so testing this bit does cover all fonts that name themselves as Italic and Oblique. (Again, this is from the below experimentation.)
|
||||
// TODO there is still one catch that might matter from a user's POV: the reverse is not true — the italic bit can be set even if the style of the font face/subfamily/style isn't named as Italic (for example, script typefaces like Adobe's Palace Script MT Std); I don't know what to do about this... I know how to start: find a script font that has an italic form (Adobe's Palace Script MT Std does not; only Regular and Semibold)
|
||||
static void setItalic(uiprivFontStyleData *d, uiFontDescriptor *out)
|
||||
{
|
||||
out->Italic = uiTextItalicNormal;
|
||||
if (([d symbolicTraits] & kCTFontItalicTrait) != 0)
|
||||
out->Italic = guessItalicOblique(d);
|
||||
}
|
||||
|
||||
static void fillDescStyleFields(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out)
|
||||
{
|
||||
setItalic(d, out);
|
||||
if (axisDict != nil)
|
||||
uiprivProcessFontVariation(d, axisDict, out);
|
||||
else
|
||||
uiprivProcessFontTraits(d, out);
|
||||
}
|
||||
|
||||
static CTFontDescriptorRef matchStyle(CTFontDescriptorRef against, uiFontDescriptor *styles)
|
||||
{
|
||||
CFArrayRef matching;
|
||||
CFIndex i, n;
|
||||
struct closeness *closeness;
|
||||
CTFontDescriptorRef current;
|
||||
CTFontDescriptorRef out;
|
||||
uiprivFontStyleData *d;
|
||||
NSDictionary *axisDict;
|
||||
|
||||
matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
|
||||
if (matching == NULL)
|
||||
// no matches; give the original back and hope for the best
|
||||
return against;
|
||||
n = CFArrayGetCount(matching);
|
||||
if (n == 0) {
|
||||
// likewise
|
||||
CFRelease(matching);
|
||||
return against;
|
||||
}
|
||||
|
||||
current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, 0);
|
||||
d = [[uiprivFontStyleData alloc] initWithDescriptor:current];
|
||||
axisDict = nil;
|
||||
if ([d variation] != NULL)
|
||||
axisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]);
|
||||
|
||||
closeness = (struct closeness *) uiprivAlloc(n * sizeof (struct closeness), "struct closeness[]");
|
||||
for (i = 0; i < n; i++) {
|
||||
uiFontDescriptor fields;
|
||||
|
||||
closeness[i].index = i;
|
||||
if (i != 0) {
|
||||
current = (CTFontDescriptorRef) CFArrayGetValueAtIndex(matching, i);
|
||||
d = [[uiprivFontStyleData alloc] initWithDescriptor:current];
|
||||
}
|
||||
fillDescStyleFields(d, axisDict, &fields);
|
||||
closeness[i].weight = fields.Weight - styles->Weight;
|
||||
closeness[i].italic = italicClosenesses[styles->Italic][fields.Italic];
|
||||
closeness[i].stretch = fields.Stretch - styles->Stretch;
|
||||
[d release];
|
||||
}
|
||||
|
||||
// now figure out the 3-space difference between the three and sort by that
|
||||
// TODO merge this loop with the previous loop?
|
||||
for (i = 0; i < n; i++) {
|
||||
double weight, italic, stretch;
|
||||
|
||||
weight = (double) (closeness[i].weight);
|
||||
weight *= weight;
|
||||
italic = closeness[i].italic;
|
||||
italic *= italic;
|
||||
stretch = (double) (closeness[i].stretch);
|
||||
stretch *= stretch;
|
||||
closeness[i].distance = sqrt(weight + italic + stretch);
|
||||
}
|
||||
qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
|
||||
const struct closeness *a = (const struct closeness *) aa;
|
||||
const struct closeness *b = (const struct closeness *) bb;
|
||||
|
||||
// via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
|
||||
// LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
|
||||
return (a->distance > b->distance) - (a->distance < b->distance);
|
||||
});
|
||||
// and the first element of the sorted array is what we want
|
||||
out = CFArrayGetValueAtIndex(matching, closeness[0].index);
|
||||
CFRetain(out); // get rule
|
||||
|
||||
// release everything
|
||||
if (axisDict != nil)
|
||||
[axisDict release];
|
||||
uiprivFree(closeness);
|
||||
CFRelease(matching);
|
||||
// and release the original descriptor since we no longer need it
|
||||
CFRelease(against);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
CTFontDescriptorRef uiprivFontDescriptorToCTFontDescriptor(uiFontDescriptor *fd)
|
||||
{
|
||||
CFMutableDictionaryRef attrs;
|
||||
CFStringRef cffamily;
|
||||
CFNumberRef cfsize;
|
||||
CTFontDescriptorRef basedesc;
|
||||
|
||||
attrs = CFDictionaryCreateMutable(NULL, 2,
|
||||
// TODO are these correct?
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
if (attrs == NULL) {
|
||||
// TODO
|
||||
}
|
||||
cffamily = CFStringCreateWithCString(NULL, fd->Family, kCFStringEncodingUTF8);
|
||||
if (cffamily == NULL) {
|
||||
// TODO
|
||||
}
|
||||
CFDictionaryAddValue(attrs, kCTFontFamilyNameAttribute, cffamily);
|
||||
CFRelease(cffamily);
|
||||
cfsize = CFNumberCreate(NULL, kCFNumberDoubleType, &(fd->Size));
|
||||
CFDictionaryAddValue(attrs, kCTFontSizeAttribute, cfsize);
|
||||
CFRelease(cfsize);
|
||||
|
||||
basedesc = CTFontDescriptorCreateWithAttributes(attrs);
|
||||
CFRelease(attrs); // TODO correct?
|
||||
return matchStyle(basedesc, fd);
|
||||
}
|
||||
|
||||
// fortunately features that aren't supported are simply ignored, so we can copy them all in
|
||||
CTFontDescriptorRef uiprivCTFontDescriptorAppendFeatures(CTFontDescriptorRef desc, const uiOpenTypeFeatures *otf)
|
||||
{
|
||||
CTFontDescriptorRef new;
|
||||
CFArrayRef featuresArray;
|
||||
CFDictionaryRef attrs;
|
||||
const void *keys[1], *values[1];
|
||||
|
||||
featuresArray = uiprivOpenTypeFeaturesToCTFeatures(otf);
|
||||
keys[0] = kCTFontFeatureSettingsAttribute;
|
||||
values[0] = featuresArray;
|
||||
attrs = CFDictionaryCreate(NULL,
|
||||
keys, values, 1,
|
||||
// TODO are these correct?
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
CFRelease(featuresArray);
|
||||
new = CTFontDescriptorCreateCopyWithAttributes(desc, attrs);
|
||||
CFRelease(attrs);
|
||||
CFRelease(desc);
|
||||
return new;
|
||||
}
|
||||
|
||||
void uiprivFontDescriptorFromCTFontDescriptor(CTFontDescriptorRef ctdesc, uiFontDescriptor *uidesc)
|
||||
{
|
||||
CFStringRef cffamily;
|
||||
uiprivFontStyleData *d;
|
||||
NSDictionary *axisDict;
|
||||
|
||||
cffamily = (CFStringRef) CTFontDescriptorCopyAttribute(ctdesc, kCTFontFamilyNameAttribute);
|
||||
if (cffamily == NULL) {
|
||||
// TODO
|
||||
}
|
||||
// TODO normalize this by adding a uiDarwinCFStringToText()
|
||||
uidesc->Family = uiDarwinNSStringToText((NSString *) cffamily);
|
||||
CFRelease(cffamily);
|
||||
|
||||
d = [[uiprivFontStyleData alloc] initWithDescriptor:ctdesc];
|
||||
axisDict = nil;
|
||||
if ([d variation] != NULL)
|
||||
axisDict = uiprivMakeVariationAxisDict([d variationAxes], [d table:kCTFontTableAvar]);
|
||||
fillDescStyleFields(d, axisDict, uidesc);
|
||||
if (axisDict != nil)
|
||||
[axisDict release];
|
||||
[d release];
|
||||
}
|
||||
223
dep/libui/darwin/fonttraits.m
Normal file
223
dep/libui/darwin/fonttraits.m
Normal file
@@ -0,0 +1,223 @@
|
||||
// 1 november 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// This is the part of the font style matching and normalization code
|
||||
// that handles fonts that use a traits dictionary.
|
||||
//
|
||||
// Matching stupidity: Core Text requires an **exact match for the
|
||||
// entire traits dictionary**, otherwise it will **drop ALL the traits**.
|
||||
//
|
||||
// Normalization stupidity: Core Text uses its own scaled values for
|
||||
// weight and width, but the values are different if the font is not
|
||||
// registered and if said font is TrueType or OpenType. The values
|
||||
// for all other cases do have some named constants starting with
|
||||
// OS X 10.11, but even these aren't very consistent in practice.
|
||||
//
|
||||
// Of course, none of this is documented anywhere, so I had to do
|
||||
// both trial-and-error AND reverse engineering to figure out what's
|
||||
// what. We'll just convert Core Text's values into libui constants
|
||||
// and use those for matching.
|
||||
|
||||
static BOOL fontRegistered(uiprivFontStyleData *d)
|
||||
{
|
||||
if (![d hasRegistrationScope])
|
||||
// header says this should be treated as the same as not registered
|
||||
return NO;
|
||||
// examination of Core Text shows this is accurate
|
||||
return [d registrationScope] != kCTFontManagerScopeNone;
|
||||
}
|
||||
|
||||
// Core Text does (usWidthClass / 10) - 0.5 here.
|
||||
// This roughly maps to our values with increments of 0.1, except for the fact 0 and 10 are allowed by Core Text, despite being banned by TrueType and OpenType themselves.
|
||||
// We'll just treat them as identical to 1 and 9, respectively.
|
||||
static const uiTextStretch os2WidthsToStretches[] = {
|
||||
uiTextStretchUltraCondensed,
|
||||
uiTextStretchUltraCondensed,
|
||||
uiTextStretchExtraCondensed,
|
||||
uiTextStretchCondensed,
|
||||
uiTextStretchSemiCondensed,
|
||||
uiTextStretchNormal,
|
||||
uiTextStretchSemiExpanded,
|
||||
uiTextStretchExpanded,
|
||||
uiTextStretchExtraExpanded,
|
||||
uiTextStretchUltraExpanded,
|
||||
uiTextStretchUltraExpanded,
|
||||
};
|
||||
|
||||
static const CFStringRef exceptions[] = {
|
||||
CFSTR("LucidaGrande"),
|
||||
CFSTR(".LucidaGrandeUI"),
|
||||
CFSTR("STHeiti"),
|
||||
CFSTR("STXihei"),
|
||||
CFSTR("TimesNewRomanPSMT"),
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void trySecondaryOS2Values(uiprivFontStyleData *d, uiFontDescriptor *out, BOOL *hasWeight, BOOL *hasWidth)
|
||||
{
|
||||
CFDataRef os2;
|
||||
uint16_t usWeightClass, usWidthClass;
|
||||
CFStringRef psname;
|
||||
const CFStringRef *ex;
|
||||
|
||||
*hasWeight = NO;
|
||||
*hasWidth = NO;
|
||||
|
||||
// only applies to unregistered fonts
|
||||
if (fontRegistered(d))
|
||||
return;
|
||||
|
||||
os2 = [d table:kCTFontTableOS2];
|
||||
if (os2 == NULL)
|
||||
// no OS2 table, so no secondary values
|
||||
return;
|
||||
|
||||
if (CFDataGetLength(os2) > 77) {
|
||||
const UInt8 *b;
|
||||
|
||||
b = CFDataGetBytePtr(os2);
|
||||
|
||||
usWeightClass = ((uint16_t) (b[4])) << 8;
|
||||
usWeightClass |= (uint16_t) (b[5]);
|
||||
if (usWeightClass <= 1000) {
|
||||
if (usWeightClass < 11)
|
||||
usWeightClass *= 100;
|
||||
*hasWeight = YES;
|
||||
}
|
||||
|
||||
usWidthClass = ((uint16_t) (b[6])) << 8;
|
||||
usWidthClass |= (uint16_t) (b[7]);
|
||||
if (usWidthClass <= 10)
|
||||
*hasWidth = YES;
|
||||
} else {
|
||||
usWeightClass = 0;
|
||||
*hasWeight = YES;
|
||||
|
||||
usWidthClass = 0;
|
||||
*hasWidth = YES;
|
||||
}
|
||||
if (*hasWeight)
|
||||
// we can just use this directly
|
||||
out->Weight = usWeightClass;
|
||||
if (*hasWidth)
|
||||
out->Stretch = os2WidthsToStretches[usWidthClass];
|
||||
CFRelease(os2);
|
||||
|
||||
// don't use secondary weights in the event of special predefined names
|
||||
psname = [d postScriptName];
|
||||
for (ex = exceptions; *ex != NULL; ex++)
|
||||
if (CFEqual(psname, *ex)) {
|
||||
*hasWeight = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL testTTFOTFSubfamilyName(CFStringRef name, CFStringRef want)
|
||||
{
|
||||
CFRange range;
|
||||
|
||||
if (name == NULL)
|
||||
return NO;
|
||||
range.location = 0;
|
||||
range.length = CFStringGetLength(name);
|
||||
return CFStringFindWithOptions(name, want, range,
|
||||
(kCFCompareCaseInsensitive | kCFCompareBackwards | kCFCompareNonliteral), NULL) != false;
|
||||
}
|
||||
|
||||
static BOOL testTTFOTFSubfamilyNames(uiprivFontStyleData *d, CFStringRef want)
|
||||
{
|
||||
switch ([d fontFormat]) {
|
||||
case kCTFontFormatOpenTypePostScript:
|
||||
case kCTFontFormatOpenTypeTrueType:
|
||||
case kCTFontFormatTrueType:
|
||||
break;
|
||||
default:
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (testTTFOTFSubfamilyName([d preferredSubFamilyName], want))
|
||||
return YES;
|
||||
if (testTTFOTFSubfamilyName([d subFamilyName], want))
|
||||
return YES;
|
||||
if (testTTFOTFSubfamilyName([d fullName], want))
|
||||
return YES;
|
||||
if (testTTFOTFSubfamilyName([d preferredFamilyName], want))
|
||||
return YES;
|
||||
return testTTFOTFSubfamilyName([d familyName], want);
|
||||
}
|
||||
|
||||
// work around a bug in libFontRegistry.dylib
|
||||
static BOOL shouldReallyBeThin(uiprivFontStyleData *d)
|
||||
{
|
||||
return testTTFOTFSubfamilyNames(d, CFSTR("W1"));
|
||||
}
|
||||
|
||||
// work around a bug in libFontRegistry.dylib
|
||||
static BOOL shouldReallyBeSemiCondensed(uiprivFontStyleData *d)
|
||||
{
|
||||
return testTTFOTFSubfamilyNames(d, CFSTR("Semi Condensed"));
|
||||
}
|
||||
|
||||
void uiprivProcessFontTraits(uiprivFontStyleData *d, uiFontDescriptor *out)
|
||||
{
|
||||
double weight, width;
|
||||
BOOL hasWeight, hasWidth;
|
||||
|
||||
hasWeight = NO;
|
||||
hasWidth = NO;
|
||||
trySecondaryOS2Values(d, out, &hasWeight, &hasWidth);
|
||||
|
||||
weight = [d weight];
|
||||
width = [d width];
|
||||
|
||||
if (!hasWeight)
|
||||
// TODO this scale is a bit lopsided
|
||||
if (weight <= -0.7)
|
||||
out->Weight = uiTextWeightThin;
|
||||
else if (weight <= -0.5)
|
||||
out->Weight = uiTextWeightUltraLight;
|
||||
else if (weight <= -0.3)
|
||||
out->Weight = uiTextWeightLight;
|
||||
else if (weight <= -0.23) {
|
||||
out->Weight = uiTextWeightBook;
|
||||
if (shouldReallyBeThin(d))
|
||||
out->Weight = uiTextWeightThin;
|
||||
} else if (weight <= 0.0)
|
||||
out->Weight = uiTextWeightNormal;
|
||||
else if (weight <= 0.23)
|
||||
out->Weight = uiTextWeightMedium;
|
||||
else if (weight <= 0.3)
|
||||
out->Weight = uiTextWeightSemiBold;
|
||||
else if (weight <= 0.4)
|
||||
out->Weight = uiTextWeightBold;
|
||||
else if (weight <= 0.5)
|
||||
out->Weight = uiTextWeightUltraBold;
|
||||
else if (weight <= 0.7)
|
||||
out->Weight = uiTextWeightHeavy;
|
||||
else
|
||||
out->Weight = uiTextWeightUltraHeavy;
|
||||
|
||||
if (!hasWidth)
|
||||
// TODO this scale is a bit lopsided
|
||||
if (width <= -0.7) {
|
||||
out->Stretch = uiTextStretchUltraCondensed;
|
||||
if (shouldReallyBeSemiCondensed(d))
|
||||
out->Stretch = uiTextStretchSemiCondensed;
|
||||
} else if (width <= -0.5)
|
||||
out->Stretch = uiTextStretchExtraCondensed;
|
||||
else if (width <= -0.2)
|
||||
out->Stretch = uiTextStretchCondensed;
|
||||
else if (width <= -0.1)
|
||||
out->Stretch = uiTextStretchSemiCondensed;
|
||||
else if (width <= 0.0)
|
||||
out->Stretch = uiTextStretchNormal;
|
||||
else if (width <= 0.1)
|
||||
out->Stretch = uiTextStretchSemiExpanded;
|
||||
else if (width <= 0.2)
|
||||
out->Stretch = uiTextStretchExpanded;
|
||||
else if (width <= 0.6)
|
||||
out->Stretch = uiTextStretchExtraExpanded;
|
||||
else
|
||||
out->Stretch = uiTextStretchUltraExpanded;
|
||||
}
|
||||
336
dep/libui/darwin/fontvariation.m
Normal file
336
dep/libui/darwin/fontvariation.m
Normal file
@@ -0,0 +1,336 @@
|
||||
// 2 november 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// This is the part of the font style matching and normalization code
|
||||
// that handles fonts that use the fvar table.
|
||||
//
|
||||
// Matching stupidity: Core Text **doesn't even bother** matching
|
||||
// these, even if you tell it to do so explicitly. It'll always return
|
||||
// all variations for a given font.
|
||||
//
|
||||
// Normalization stupidity: Core Text doesn't normalize the fvar
|
||||
// table values for us, so we'll have to do it ourselves. Furthermore,
|
||||
// Core Text doesn't provide an API for accessing the avar table, if
|
||||
// any, so we must do so ourselves. (TODO does Core Text even
|
||||
// follow the avar table if a font has it?)
|
||||
//
|
||||
// Thankfully, normalization is well-defined in both TrueType and
|
||||
// OpenType and seems identical in both, so we can just normalize
|
||||
// the values and then convert them linearly to libui values for
|
||||
// matching.
|
||||
//
|
||||
// References:
|
||||
// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html
|
||||
// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6avar.html
|
||||
// - https://www.microsoft.com/typography/otspec/fvar.htm
|
||||
// - https://www.microsoft.com/typography/otspec/otvaroverview.htm#CSN
|
||||
// - https://www.microsoft.com/typography/otspec/otff.htm
|
||||
// - https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html#Types
|
||||
// - https://www.microsoft.com/typography/otspec/avar.htm
|
||||
|
||||
// TODO Skia doesn't quite map correctly; notice what passes for condensed in the drawtext example
|
||||
// TODO also investigate Marker Felt not working right in Thin and Wide modes (but that's probably the other file, putting it here just so I don't forget)
|
||||
|
||||
#define fvarWeight 0x77676874
|
||||
#define fvarWidth 0x77647468
|
||||
|
||||
// TODO explain why these are signed
|
||||
typedef int32_t fixed1616;
|
||||
typedef int16_t fixed214;
|
||||
|
||||
// note that Microsoft's data type list implies that *all* fixed-point types have the same format; it only gives specific examples for the 2.14 format, which confused me because I thought 16.16 worked differently, but eh
|
||||
static fixed1616 doubleToFixed1616(double d)
|
||||
{
|
||||
double ipart, fpart;
|
||||
long flong;
|
||||
int16_t i16;
|
||||
uint32_t ret;
|
||||
|
||||
fpart = modf(d, &ipart);
|
||||
// fpart must be unsigned; modf() gives us fpart with the same sign as d (so we have to adjust both ipart and fpart appropriately)
|
||||
if (fpart < 0) {
|
||||
ipart -= 1;
|
||||
fpart = 1 + fpart;
|
||||
}
|
||||
fpart *= 65536;
|
||||
flong = lround(fpart);
|
||||
i16 = (int16_t) ipart;
|
||||
ret = (uint32_t) ((uint16_t) i16);
|
||||
ret <<= 16;
|
||||
ret |= (uint16_t) (flong & 0xFFFF);
|
||||
return (fixed1616) ret;
|
||||
}
|
||||
|
||||
// see also https://stackoverflow.com/questions/8506317/fixed-point-unsigned-division-in-c and freetype's FT_DivFix()
|
||||
// TODO figure out the specifics of freetype's more complex implementation that shifts b and juggles signs
|
||||
static fixed1616 fixed1616Divide(fixed1616 a, fixed1616 b)
|
||||
{
|
||||
uint32_t u;
|
||||
int64_t a64;
|
||||
|
||||
u = (uint32_t) a;
|
||||
a64 = (int64_t) (((uint64_t) u) << 16);
|
||||
return (fixed1616) (a64 / b);
|
||||
}
|
||||
|
||||
static fixed214 fixed1616ToFixed214(fixed1616 f)
|
||||
{
|
||||
uint32_t t;
|
||||
uint32_t topbit;
|
||||
|
||||
t = (uint32_t) (f + 0x00000002);
|
||||
topbit = t & 0x80000000;
|
||||
t >>= 2;
|
||||
if (topbit != 0)
|
||||
t |= 0xC000000;
|
||||
return (fixed214) (t & 0xFFFF);
|
||||
}
|
||||
|
||||
static double fixed214ToDouble(fixed214 f)
|
||||
{
|
||||
uint16_t u;
|
||||
double base;
|
||||
double frac;
|
||||
|
||||
u = (uint16_t) f;
|
||||
switch ((u >> 14) & 0x3) {
|
||||
case 0:
|
||||
base = 0;
|
||||
break;
|
||||
case 1:
|
||||
base = 1;
|
||||
break;
|
||||
case 2:
|
||||
base = -2;
|
||||
break;
|
||||
case 3:
|
||||
base = -1;
|
||||
}
|
||||
frac = ((double) (u & 0x3FFF)) / 16384;
|
||||
return base + frac;
|
||||
}
|
||||
|
||||
static fixed1616 fixed214ToFixed1616(fixed214 f)
|
||||
{
|
||||
int32_t t;
|
||||
|
||||
t = (int32_t) ((int16_t) f);
|
||||
t <<= 2;
|
||||
return (fixed1616) (t - 0x00000002);
|
||||
}
|
||||
|
||||
static const fixed1616 fixed1616Negative1 = (int32_t) ((uint32_t) 0xFFFF0000);
|
||||
static const fixed1616 fixed1616Zero = 0x00000000;
|
||||
static const fixed1616 fixed1616Positive1 = 0x00010000;
|
||||
|
||||
static fixed1616 fixed1616Normalize(fixed1616 val, fixed1616 min, fixed1616 max, fixed1616 def)
|
||||
{
|
||||
if (val < min)
|
||||
val = min;
|
||||
if (val > max)
|
||||
val = max;
|
||||
if (val < def)
|
||||
return fixed1616Divide(-(def - val), (def - min));
|
||||
if (val > def)
|
||||
return fixed1616Divide((val - def), (max - def));
|
||||
return fixed1616Zero;
|
||||
}
|
||||
|
||||
static fixed214 normalizedTo214(fixed1616 val, const fixed1616 *avarMappings, size_t avarCount)
|
||||
{
|
||||
if (val < fixed1616Negative1)
|
||||
val = fixed1616Negative1;
|
||||
if (val > fixed1616Positive1)
|
||||
val = fixed1616Positive1;
|
||||
if (avarCount != 0) {
|
||||
size_t start, end;
|
||||
fixed1616 startFrom, endFrom;
|
||||
fixed1616 startTo, endTo;
|
||||
|
||||
for (end = 0; end < avarCount; end += 2) {
|
||||
endFrom = avarMappings[end];
|
||||
endTo = avarMappings[end + 1];
|
||||
if (endFrom >= val)
|
||||
break;
|
||||
}
|
||||
if (endFrom == val)
|
||||
val = endTo;
|
||||
else {
|
||||
start = end - 2;
|
||||
startFrom = avarMappings[start];
|
||||
startTo = avarMappings[start + 1];
|
||||
val = fixed1616Divide((val - startFrom), (endFrom - startFrom));
|
||||
// TODO find a font with an avar table and make sure this works, or if we need to use special code for this too
|
||||
val *= (endTo - startTo);
|
||||
val += startTo;
|
||||
}
|
||||
}
|
||||
return fixed1616ToFixed214(val);
|
||||
}
|
||||
|
||||
static fixed1616 *avarExtract(CFDataRef table, CFIndex index, size_t *n)
|
||||
{
|
||||
const UInt8 *b;
|
||||
size_t off;
|
||||
size_t i, nEntries;
|
||||
fixed1616 *entries;
|
||||
fixed1616 *p;
|
||||
|
||||
b = CFDataGetBytePtr(table);
|
||||
off = 8;
|
||||
#define nextuint16be() ((((uint16_t) (b[off])) << 8) | ((uint16_t) (b[off + 1])))
|
||||
for (; index > 0; index--) {
|
||||
nEntries = (size_t) nextuint16be();
|
||||
off += 2;
|
||||
off += 4 * nEntries;
|
||||
}
|
||||
nEntries = nextuint16be();
|
||||
*n = nEntries * 2;
|
||||
entries = (fixed1616 *) uiprivAlloc(*n * sizeof (fixed1616), "fixed1616[]");
|
||||
p = entries;
|
||||
for (i = 0; i < *n; i++) {
|
||||
*p++ = fixed214ToFixed1616((fixed214) nextuint16be());
|
||||
off += 2;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
static BOOL extractAxisDictValue(CFDictionaryRef dict, CFStringRef key, fixed1616 *out)
|
||||
{
|
||||
CFNumberRef num;
|
||||
double v;
|
||||
|
||||
num = (CFNumberRef) CFDictionaryGetValue(dict, key);
|
||||
if (CFNumberGetValue(num, kCFNumberDoubleType, &v) == false)
|
||||
return NO;
|
||||
*out = doubleToFixed1616(v);
|
||||
return YES;
|
||||
}
|
||||
|
||||
// TODO here and elsewhere: make sure all Objective-C classes and possibly also custom method names have uipriv prefixes
|
||||
@interface fvarAxis : NSObject {
|
||||
fixed1616 min;
|
||||
fixed1616 max;
|
||||
fixed1616 def;
|
||||
fixed1616 *avarMappings;
|
||||
size_t avarCount;
|
||||
}
|
||||
- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table;
|
||||
- (double)normalize:(double)v;
|
||||
@end
|
||||
|
||||
@implementation fvarAxis
|
||||
|
||||
- (id)initWithIndex:(CFIndex)i dict:(CFDictionaryRef)dict avarTable:(CFDataRef)table
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->avarMappings = NULL;
|
||||
self->avarCount = 0;
|
||||
if (!extractAxisDictValue(dict, kCTFontVariationAxisMinimumValueKey, &(self->min)))
|
||||
goto fail;
|
||||
if (!extractAxisDictValue(dict, kCTFontVariationAxisMaximumValueKey, &(self->max)))
|
||||
goto fail;
|
||||
if (!extractAxisDictValue(dict, kCTFontVariationAxisDefaultValueKey, &(self->def)))
|
||||
goto fail;
|
||||
if (table != NULL)
|
||||
self->avarMappings = avarExtract(table, i, &(self->avarCount));
|
||||
}
|
||||
return self;
|
||||
|
||||
fail:
|
||||
[self release];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (self->avarMappings != NULL) {
|
||||
uiprivFree(self->avarMappings);
|
||||
self->avarMappings = NULL;
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (double)normalize:(double)d
|
||||
{
|
||||
fixed1616 n;
|
||||
fixed214 n2;
|
||||
|
||||
n = doubleToFixed1616(d);
|
||||
n = fixed1616Normalize(n, self->min, self->max, self->def);
|
||||
n2 = normalizedTo214(n, self->avarMappings, self->avarCount);
|
||||
return fixed214ToDouble(n2);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSDictionary *uiprivMakeVariationAxisDict(CFArrayRef axes, CFDataRef avarTable)
|
||||
{
|
||||
CFDictionaryRef axis;
|
||||
CFIndex i, n;
|
||||
NSMutableDictionary *out;
|
||||
|
||||
n = CFArrayGetCount(axes);
|
||||
out = [NSMutableDictionary new];
|
||||
for (i = 0; i < n; i++) {
|
||||
CFNumberRef key;
|
||||
|
||||
axis = (CFDictionaryRef) CFArrayGetValueAtIndex(axes, i);
|
||||
key = (CFNumberRef) CFDictionaryGetValue(axis, kCTFontVariationAxisIdentifierKey);
|
||||
[out setObject:[[fvarAxis alloc] initWithIndex:i dict:axis avarTable:avarTable]
|
||||
forKey:((NSNumber *) key)];
|
||||
}
|
||||
if (avarTable != NULL)
|
||||
CFRelease(avarTable);
|
||||
return out;
|
||||
}
|
||||
|
||||
#define fvarAxisKey(n) [NSNumber numberWithUnsignedInteger:n]
|
||||
|
||||
static BOOL tryAxis(NSDictionary *axisDict, CFDictionaryRef var, NSNumber *key, double *out)
|
||||
{
|
||||
fvarAxis *axis;
|
||||
CFNumberRef num;
|
||||
|
||||
axis = (fvarAxis *) [axisDict objectForKey:key];
|
||||
if (axis == nil)
|
||||
return NO;
|
||||
num = (CFNumberRef) CFDictionaryGetValue(var, (CFNumberRef) key);
|
||||
if (num == nil)
|
||||
return NO;
|
||||
if (CFNumberGetValue(num, kCFNumberDoubleType, out) == false) {
|
||||
// TODO
|
||||
return NO;
|
||||
}
|
||||
*out = [axis normalize:*out];
|
||||
return YES;
|
||||
}
|
||||
|
||||
void uiprivProcessFontVariation(uiprivFontStyleData *d, NSDictionary *axisDict, uiFontDescriptor *out)
|
||||
{
|
||||
CFDictionaryRef var;
|
||||
double v;
|
||||
|
||||
out->Weight = uiTextWeightNormal;
|
||||
out->Stretch = uiTextStretchNormal;
|
||||
|
||||
var = [d variation];
|
||||
|
||||
if (tryAxis(axisDict, var, fvarAxisKey(fvarWeight), &v)) {
|
||||
// v is now a value between -1 and 1 scaled linearly between discrete points
|
||||
// we want a linear value between 0 and 1000 with 400 being normal
|
||||
if (v < 0) {
|
||||
v += 1;
|
||||
out->Weight = (uiTextWeight) (v * 400);
|
||||
} else if (v > 0)
|
||||
out->Weight += (uiTextWeight) (v * 600);
|
||||
}
|
||||
|
||||
if (tryAxis(axisDict, var, fvarAxisKey(fvarWidth), &v)) {
|
||||
// likewise, but with stretches, we go from 0 to 8 with 4 being directly between the two, so this is sufficient
|
||||
v += 1;
|
||||
out->Stretch = (uiTextStretch) (v * 4);
|
||||
}
|
||||
}
|
||||
561
dep/libui/darwin/form.m
Normal file
561
dep/libui/darwin/form.m
Normal file
@@ -0,0 +1,561 @@
|
||||
// 7 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO in the test program, sometimes one of the radio buttons can disappear (try when spaced)
|
||||
|
||||
@interface formChild : NSView
|
||||
@property uiControl *c;
|
||||
@property (strong) NSTextField *label;
|
||||
@property BOOL stretchy;
|
||||
@property NSLayoutPriority oldHorzHuggingPri;
|
||||
@property NSLayoutPriority oldVertHuggingPri;
|
||||
@property (strong) NSLayoutConstraint *baseline;
|
||||
@property (strong) NSLayoutConstraint *leading;
|
||||
@property (strong) NSLayoutConstraint *top;
|
||||
@property (strong) NSLayoutConstraint *trailing;
|
||||
@property (strong) NSLayoutConstraint *bottom;
|
||||
- (id)initWithLabel:(NSTextField *)l;
|
||||
- (void)onDestroy;
|
||||
- (NSView *)view;
|
||||
@end
|
||||
|
||||
@interface formView : NSView {
|
||||
uiForm *f;
|
||||
NSMutableArray *children;
|
||||
int padded;
|
||||
|
||||
NSLayoutConstraint *first;
|
||||
NSMutableArray *inBetweens;
|
||||
NSLayoutConstraint *last;
|
||||
NSMutableArray *widths;
|
||||
NSMutableArray *leadings;
|
||||
NSMutableArray *middles;
|
||||
NSMutableArray *trailings;
|
||||
}
|
||||
- (id)initWithF:(uiForm *)ff;
|
||||
- (void)onDestroy;
|
||||
- (void)removeOurConstraints;
|
||||
- (void)syncEnableStates:(int)enabled;
|
||||
- (CGFloat)paddingAmount;
|
||||
- (void)establishOurConstraints;
|
||||
- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy;
|
||||
- (void)delete:(int)n;
|
||||
- (int)isPadded;
|
||||
- (void)setPadded:(int)p;
|
||||
- (BOOL)hugsTrailing;
|
||||
- (BOOL)hugsBottom;
|
||||
- (int)nStretchy;
|
||||
@end
|
||||
|
||||
struct uiForm {
|
||||
uiDarwinControl c;
|
||||
formView *view;
|
||||
};
|
||||
|
||||
@implementation formChild
|
||||
|
||||
- (id)initWithLabel:(NSTextField *)l
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self) {
|
||||
self.label = l;
|
||||
[self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
|
||||
[self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
|
||||
[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
|
||||
[self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
|
||||
[self addSubview:self.label];
|
||||
|
||||
self.leading = uiprivMkConstraint(self.label, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationGreaterThanOrEqual,
|
||||
self, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiForm label leading");
|
||||
[self addConstraint:self.leading];
|
||||
self.top = uiprivMkConstraint(self.label, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiForm label top");
|
||||
[self addConstraint:self.top];
|
||||
self.trailing = uiprivMkConstraint(self.label, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiForm label trailing");
|
||||
[self addConstraint:self.trailing];
|
||||
self.bottom = uiprivMkConstraint(self.label, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiForm label bottom");
|
||||
[self addConstraint:self.bottom];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
[self removeConstraint:self.trailing];
|
||||
self.trailing = nil;
|
||||
[self removeConstraint:self.top];
|
||||
self.top = nil;
|
||||
[self removeConstraint:self.bottom];
|
||||
self.bottom = nil;
|
||||
|
||||
[self.label removeFromSuperview];
|
||||
self.label = nil;
|
||||
}
|
||||
|
||||
- (NSView *)view
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation formView
|
||||
|
||||
- (id)initWithF:(uiForm *)ff
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self != nil) {
|
||||
self->f = ff;
|
||||
self->padded = 0;
|
||||
self->children = [NSMutableArray new];
|
||||
|
||||
self->inBetweens = [NSMutableArray new];
|
||||
self->widths = [NSMutableArray new];
|
||||
self->leadings = [NSMutableArray new];
|
||||
self->middles = [NSMutableArray new];
|
||||
self->trailings = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
formChild *fc;
|
||||
|
||||
[self removeOurConstraints];
|
||||
[self->inBetweens release];
|
||||
[self->widths release];
|
||||
[self->leadings release];
|
||||
[self->middles release];
|
||||
[self->trailings release];
|
||||
|
||||
for (fc in self->children) {
|
||||
[self removeConstraint:fc.baseline];
|
||||
fc.baseline = nil;
|
||||
uiControlSetParent(fc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
|
||||
uiControlDestroy(fc.c);
|
||||
[fc onDestroy];
|
||||
[fc removeFromSuperview];
|
||||
}
|
||||
[self->children release];
|
||||
}
|
||||
|
||||
- (void)removeOurConstraints
|
||||
{
|
||||
if (self->first != nil) {
|
||||
[self removeConstraint:self->first];
|
||||
[self->first release];
|
||||
self->first = nil;
|
||||
}
|
||||
if ([self->inBetweens count] != 0) {
|
||||
[self removeConstraints:self->inBetweens];
|
||||
[self->inBetweens removeAllObjects];
|
||||
}
|
||||
if (self->last != nil) {
|
||||
[self removeConstraint:self->last];
|
||||
[self->last release];
|
||||
self->last = nil;
|
||||
}
|
||||
if ([self->widths count] != 0) {
|
||||
[self removeConstraints:self->widths];
|
||||
[self->widths removeAllObjects];
|
||||
}
|
||||
if ([self->leadings count] != 0) {
|
||||
[self removeConstraints:self->leadings];
|
||||
[self->leadings removeAllObjects];
|
||||
}
|
||||
if ([self->middles count] != 0) {
|
||||
[self removeConstraints:self->middles];
|
||||
[self->middles removeAllObjects];
|
||||
}
|
||||
if ([self->trailings count] != 0) {
|
||||
[self removeConstraints:self->trailings];
|
||||
[self->trailings removeAllObjects];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)syncEnableStates:(int)enabled
|
||||
{
|
||||
formChild *fc;
|
||||
|
||||
for (fc in self->children)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), enabled);
|
||||
}
|
||||
|
||||
- (CGFloat)paddingAmount
|
||||
{
|
||||
if (!self->padded)
|
||||
return 0.0;
|
||||
return uiDarwinPaddingAmount(NULL);
|
||||
}
|
||||
|
||||
- (void)establishOurConstraints
|
||||
{
|
||||
formChild *fc;
|
||||
CGFloat padding;
|
||||
NSView *prev, *prevlabel;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
[self removeOurConstraints];
|
||||
if ([self->children count] == 0)
|
||||
return;
|
||||
padding = [self paddingAmount];
|
||||
|
||||
// first arrange the children vertically and make them the same width
|
||||
prev = nil;
|
||||
for (fc in self->children) {
|
||||
[fc setHidden:!uiControlVisible(fc.c)];
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
if (prev == nil) { // first view
|
||||
self->first = uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiForm first vertical constraint");
|
||||
[self addConstraint:self->first];
|
||||
[self->first retain];
|
||||
prev = [fc view];
|
||||
prevlabel = fc;
|
||||
continue;
|
||||
}
|
||||
// not the first; link it
|
||||
c = uiprivMkConstraint(prev, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeTop,
|
||||
1, -padding,
|
||||
@"uiForm in-between vertical constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
// and make the same width
|
||||
c = uiprivMkConstraint(prev, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeWidth,
|
||||
1, 0,
|
||||
@"uiForm control width constraint");
|
||||
[self addConstraint:c];
|
||||
[self->widths addObject:c];
|
||||
c = uiprivMkConstraint(prevlabel, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
fc, NSLayoutAttributeWidth,
|
||||
1, 0,
|
||||
@"uiForm label lwidth constraint");
|
||||
[self addConstraint:c];
|
||||
[self->widths addObject:c];
|
||||
prev = [fc view];
|
||||
prevlabel = fc;
|
||||
}
|
||||
if (prev == nil) // all hidden; act as if nothing there
|
||||
return;
|
||||
self->last = uiprivMkConstraint(prev, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiForm last vertical constraint");
|
||||
[self addConstraint:self->last];
|
||||
[self->last retain];
|
||||
|
||||
// now arrange the controls horizontally
|
||||
for (fc in self->children) {
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
c = uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
fc, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiForm leading constraint");
|
||||
[self addConstraint:c];
|
||||
[self->leadings addObject:c];
|
||||
// coerce the control to be as wide as possible
|
||||
// see http://stackoverflow.com/questions/37710892/in-auto-layout-i-set-up-labels-that-shouldnt-grow-horizontally-and-controls-th
|
||||
c = uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiForm leading constraint");
|
||||
[c setPriority:NSLayoutPriorityDefaultHigh];
|
||||
[self addConstraint:c];
|
||||
[self->leadings addObject:c];
|
||||
c = uiprivMkConstraint(fc, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], NSLayoutAttributeLeading,
|
||||
1, -padding,
|
||||
@"uiForm middle constraint");
|
||||
[self addConstraint:c];
|
||||
[self->middles addObject:c];
|
||||
c = uiprivMkConstraint([fc view], NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiForm trailing constraint");
|
||||
[self addConstraint:c];
|
||||
[self->trailings addObject:c];
|
||||
// TODO
|
||||
c = uiprivMkConstraint(fc, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationLessThanOrEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"TODO");
|
||||
[self addConstraint:c];
|
||||
[self->trailings addObject:c];
|
||||
}
|
||||
|
||||
// and make all stretchy controls have the same height
|
||||
prev = nil;
|
||||
for (fc in self->children) {
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
if (!fc.stretchy)
|
||||
continue;
|
||||
if (prev == nil) {
|
||||
prev = [fc view];
|
||||
continue;
|
||||
}
|
||||
c = uiprivMkConstraint([fc view], NSLayoutAttributeHeight,
|
||||
NSLayoutRelationEqual,
|
||||
prev, NSLayoutAttributeHeight,
|
||||
1, 0,
|
||||
@"uiForm stretchy constraint");
|
||||
[self addConstraint:c];
|
||||
// TODO make a dedicated array for this
|
||||
[self->leadings addObject:c];
|
||||
}
|
||||
|
||||
// we don't arrange the labels vertically; that's done when we add the control since those constraints don't need to change (they just need to be at their baseline)
|
||||
}
|
||||
|
||||
- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy
|
||||
{
|
||||
formChild *fc;
|
||||
NSLayoutPriority priority;
|
||||
NSLayoutAttribute attribute;
|
||||
int oldnStretchy;
|
||||
|
||||
fc = [[formChild alloc] initWithLabel:uiprivNewLabel(label)];
|
||||
fc.c = c;
|
||||
fc.stretchy = stretchy;
|
||||
fc.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationHorizontal);
|
||||
fc.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationVertical);
|
||||
[fc setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:fc];
|
||||
|
||||
uiControlSetParent(fc.c, uiControl(self->f));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(fc.c), self);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), uiControlEnabledToUser(uiControl(self->f)));
|
||||
|
||||
// if a control is stretchy, it should not hug vertically
|
||||
// otherwise, it should *forcibly* hug
|
||||
if (fc.stretchy)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
// LONGTERM will default high work?
|
||||
priority = NSLayoutPriorityRequired;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), priority, NSLayoutConstraintOrientationVertical);
|
||||
// make sure controls don't hug their horizontal direction so they fill the width of the view
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
||||
|
||||
// and constrain the baselines to position the label vertically
|
||||
// if the view is a scroll view, align tops, not baselines
|
||||
// this is what Interface Builder does
|
||||
attribute = NSLayoutAttributeBaseline;
|
||||
if ([[fc view] isKindOfClass:[NSScrollView class]])
|
||||
attribute = NSLayoutAttributeTop;
|
||||
fc.baseline = uiprivMkConstraint(fc.label, attribute,
|
||||
NSLayoutRelationEqual,
|
||||
[fc view], attribute,
|
||||
1, 0,
|
||||
@"uiForm baseline constraint");
|
||||
[self addConstraint:fc.baseline];
|
||||
|
||||
oldnStretchy = [self nStretchy];
|
||||
[self->children addObject:fc];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (fc.stretchy)
|
||||
if (oldnStretchy == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));
|
||||
|
||||
[fc release]; // we don't need the initial reference now
|
||||
}
|
||||
|
||||
- (void)delete:(int)n
|
||||
{
|
||||
formChild *fc;
|
||||
int stretchy;
|
||||
|
||||
fc = (formChild *) [self->children objectAtIndex:n];
|
||||
stretchy = fc.stretchy;
|
||||
|
||||
uiControlSetParent(fc.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
|
||||
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
|
||||
[fc onDestroy];
|
||||
[self->children removeObjectAtIndex:n];
|
||||
|
||||
[self establishOurConstraints];
|
||||
if (stretchy)
|
||||
if ([self nStretchy] == 0)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));
|
||||
}
|
||||
|
||||
- (int)isPadded
|
||||
{
|
||||
return self->padded;
|
||||
}
|
||||
|
||||
- (void)setPadded:(int)p
|
||||
{
|
||||
CGFloat padding;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
self->padded = p;
|
||||
padding = [self paddingAmount];
|
||||
for (c in self->inBetweens)
|
||||
[c setConstant:-padding];
|
||||
for (c in self->middles)
|
||||
[c setConstant:-padding];
|
||||
}
|
||||
|
||||
- (BOOL)hugsTrailing
|
||||
{
|
||||
return YES; // always hug trailing
|
||||
}
|
||||
|
||||
- (BOOL)hugsBottom
|
||||
{
|
||||
// only hug if we have stretchy
|
||||
return [self nStretchy] != 0;
|
||||
}
|
||||
|
||||
- (int)nStretchy
|
||||
{
|
||||
formChild *fc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (fc in self->children) {
|
||||
if (!uiControlVisible(fc.c))
|
||||
continue;
|
||||
if (fc.stretchy)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiFormDestroy(uiControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
[f->view onDestroy];
|
||||
[f->view release];
|
||||
uiFreeControl(uiControl(f));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiForm, view)
|
||||
uiDarwinControlDefaultParent(uiForm, view)
|
||||
uiDarwinControlDefaultSetParent(uiForm, view)
|
||||
uiDarwinControlDefaultToplevel(uiForm, view)
|
||||
uiDarwinControlDefaultVisible(uiForm, view)
|
||||
uiDarwinControlDefaultShow(uiForm, view)
|
||||
uiDarwinControlDefaultHide(uiForm, view)
|
||||
uiDarwinControlDefaultEnabled(uiForm, view)
|
||||
uiDarwinControlDefaultEnable(uiForm, view)
|
||||
uiDarwinControlDefaultDisable(uiForm, view)
|
||||
|
||||
static void uiFormSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(f), enabled))
|
||||
return;
|
||||
[f->view syncEnableStates:enabled];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiForm, view)
|
||||
|
||||
static BOOL uiFormHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
return [f->view hugsTrailing];
|
||||
}
|
||||
|
||||
static BOOL uiFormHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
return [f->view hugsBottom];
|
||||
}
|
||||
|
||||
static void uiFormChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
[f->view establishOurConstraints];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHuggingPriority(uiForm, view)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiForm, view)
|
||||
|
||||
static void uiFormChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiForm *f = uiForm(c);
|
||||
|
||||
[f->view establishOurConstraints];
|
||||
}
|
||||
|
||||
void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)
|
||||
{
|
||||
// LONGTERM on other platforms
|
||||
// or at leat allow this and implicitly turn it into a spacer
|
||||
if (c == NULL)
|
||||
uiprivUserBug("You cannot add NULL to a uiForm.");
|
||||
[f->view append:uiprivToNSString(label) c:c stretchy:stretchy];
|
||||
}
|
||||
|
||||
void uiFormDelete(uiForm *f, int n)
|
||||
{
|
||||
[f->view delete:n];
|
||||
}
|
||||
|
||||
int uiFormPadded(uiForm *f)
|
||||
{
|
||||
return [f->view isPadded];
|
||||
}
|
||||
|
||||
void uiFormSetPadded(uiForm *f, int padded)
|
||||
{
|
||||
[f->view setPadded:padded];
|
||||
}
|
||||
|
||||
uiForm *uiNewForm(void)
|
||||
{
|
||||
uiForm *f;
|
||||
|
||||
uiDarwinNewControl(uiForm, f);
|
||||
|
||||
f->view = [[formView alloc] initWithF:f];
|
||||
|
||||
return f;
|
||||
}
|
||||
53
dep/libui/darwin/future.m
Normal file
53
dep/libui/darwin/future.m
Normal file
@@ -0,0 +1,53 @@
|
||||
// 19 may 2017
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// functions and constants FROM THE FUTURE!
|
||||
// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName
|
||||
|
||||
// added in OS X 10.10; we need 10.8
|
||||
CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureTag = NULL;
|
||||
CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureValue = NULL;
|
||||
|
||||
// added in OS X 10.12; we need 10.8
|
||||
CFStringRef *uiprivFUTURE_kCTBackgroundColorAttributeName = NULL;
|
||||
|
||||
// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed)
|
||||
void uiprivLoadFutures(void)
|
||||
{
|
||||
void *handle;
|
||||
|
||||
// dlsym() walks the dependency chain, so opening the current process should be sufficient
|
||||
handle = dlopen(NULL, RTLD_LAZY);
|
||||
if (handle == NULL)
|
||||
return;
|
||||
#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)
|
||||
GET(uiprivFUTURE_kCTFontOpenTypeFeatureTag, kCTFontOpenTypeFeatureTag);
|
||||
GET(uiprivFUTURE_kCTFontOpenTypeFeatureValue, kCTFontOpenTypeFeatureValue);
|
||||
GET(uiprivFUTURE_kCTBackgroundColorAttributeName, kCTBackgroundColorAttributeName);
|
||||
dlclose(handle);
|
||||
}
|
||||
|
||||
// wrappers for methods that exist in the future that we can check for with respondsToSelector:
|
||||
// keep them in one place for convenience
|
||||
|
||||
// apparently only added in 10.9; we need 10.8
|
||||
void uiprivFUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier)
|
||||
{
|
||||
id cid = (id) constraint;
|
||||
|
||||
if ([constraint respondsToSelector:@selector(setIdentifier:)])
|
||||
[cid setIdentifier:identifier];
|
||||
}
|
||||
|
||||
// added in 10.11; we need 10.8
|
||||
// return whether this was done because we recreate its effects if not (see winmoveresize.m)
|
||||
BOOL uiprivFUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent)
|
||||
{
|
||||
id cw = (id) w;
|
||||
|
||||
if ([w respondsToSelector:@selector(performWindowDragWithEvent:)]) {
|
||||
[cw performWindowDragWithEvent:initialEvent];
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
59
dep/libui/darwin/graphemes.m
Normal file
59
dep/libui/darwin/graphemes.m
Normal file
@@ -0,0 +1,59 @@
|
||||
// 3 december 2016
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
// CFStringGetRangeOfComposedCharactersAtIndex() is the function for grapheme clusters
|
||||
// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway
|
||||
|
||||
int uiprivGraphemesTakesUTF16(void)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
uiprivGraphemes *uiprivNewGraphemes(void *s, size_t len)
|
||||
{
|
||||
uiprivGraphemes *g;
|
||||
UniChar *str = (UniChar *) s;
|
||||
CFStringRef cfstr;
|
||||
size_t ppos, gpos;
|
||||
CFRange range;
|
||||
size_t i;
|
||||
|
||||
g = uiprivNew(uiprivGraphemes);
|
||||
|
||||
cfstr = CFStringCreateWithCharactersNoCopy(NULL, str, len, kCFAllocatorNull);
|
||||
if (cfstr == NULL) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// first figure out how many graphemes there are
|
||||
g->len = 0;
|
||||
ppos = 0;
|
||||
while (ppos < len) {
|
||||
range = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos);
|
||||
g->len++;
|
||||
ppos = range.location + range.length;
|
||||
}
|
||||
|
||||
g->pointsToGraphemes = (size_t *) uiprivAlloc((len + 1) * sizeof (size_t), "size_t[] (graphemes)");
|
||||
g->graphemesToPoints = (size_t *) uiprivAlloc((g->len + 1) * sizeof (size_t), "size_t[] (graphemes)");
|
||||
|
||||
// now calculate everything
|
||||
// fortunately due to the use of CFRange we can do this in one loop trivially!
|
||||
ppos = 0;
|
||||
gpos = 0;
|
||||
while (ppos < len) {
|
||||
range = CFStringGetRangeOfComposedCharactersAtIndex(cfstr, ppos);
|
||||
for (i = 0; i < range.length; i++)
|
||||
g->pointsToGraphemes[range.location + i] = gpos;
|
||||
g->graphemesToPoints[gpos] = range.location;
|
||||
gpos++;
|
||||
ppos = range.location + range.length;
|
||||
}
|
||||
// and set the last one
|
||||
g->pointsToGraphemes[ppos] = gpos;
|
||||
g->graphemesToPoints[gpos] = ppos;
|
||||
|
||||
CFRelease(cfstr);
|
||||
return g;
|
||||
}
|
||||
800
dep/libui/darwin/grid.m
Normal file
800
dep/libui/darwin/grid.m
Normal file
@@ -0,0 +1,800 @@
|
||||
// 11 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO the assorted test doesn't work right at all
|
||||
|
||||
@interface gridChild : NSView
|
||||
@property uiControl *c;
|
||||
@property int left;
|
||||
@property int top;
|
||||
@property int xspan;
|
||||
@property int yspan;
|
||||
@property int hexpand;
|
||||
@property uiAlign halign;
|
||||
@property int vexpand;
|
||||
@property uiAlign valign;
|
||||
|
||||
@property (strong) NSLayoutConstraint *leadingc;
|
||||
@property (strong) NSLayoutConstraint *topc;
|
||||
@property (strong) NSLayoutConstraint *trailingc;
|
||||
@property (strong) NSLayoutConstraint *bottomc;
|
||||
@property (strong) NSLayoutConstraint *xcenterc;
|
||||
@property (strong) NSLayoutConstraint *ycenterc;
|
||||
|
||||
@property NSLayoutPriority oldHorzHuggingPri;
|
||||
@property NSLayoutPriority oldVertHuggingPri;
|
||||
- (void)setC:(uiControl *)c grid:(uiGrid *)g;
|
||||
- (void)onDestroy;
|
||||
- (NSView *)view;
|
||||
@end
|
||||
|
||||
@interface gridView : NSView {
|
||||
uiGrid *g;
|
||||
NSMutableArray *children;
|
||||
int padded;
|
||||
|
||||
NSMutableArray *edges;
|
||||
NSMutableArray *inBetweens;
|
||||
|
||||
NSMutableArray *emptyCellViews;
|
||||
}
|
||||
- (id)initWithG:(uiGrid *)gg;
|
||||
- (void)onDestroy;
|
||||
- (void)removeOurConstraints;
|
||||
- (void)syncEnableStates:(int)enabled;
|
||||
- (CGFloat)paddingAmount;
|
||||
- (void)establishOurConstraints;
|
||||
- (void)append:(gridChild *)gc;
|
||||
- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at;
|
||||
- (int)isPadded;
|
||||
- (void)setPadded:(int)p;
|
||||
- (BOOL)hugsTrailing;
|
||||
- (BOOL)hugsBottom;
|
||||
- (int)nhexpand;
|
||||
- (int)nvexpand;
|
||||
@end
|
||||
|
||||
struct uiGrid {
|
||||
uiDarwinControl c;
|
||||
gridView *view;
|
||||
};
|
||||
|
||||
@implementation gridChild
|
||||
|
||||
- (void)setC:(uiControl *)c grid:(uiGrid *)g
|
||||
{
|
||||
self.c = c;
|
||||
self.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationHorizontal);
|
||||
self.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationVertical);
|
||||
|
||||
uiControlSetParent(self.c, uiControl(g));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(self.c), self);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(self.c), uiControlEnabledToUser(uiControl(g)));
|
||||
|
||||
if (self.halign == uiAlignStart || self.halign == uiAlignFill) {
|
||||
self.leadingc = uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiGrid child horizontal alignment start constraint");
|
||||
[self addConstraint:self.leadingc];
|
||||
}
|
||||
if (self.halign == uiAlignCenter) {
|
||||
self.xcenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterX,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeCenterX,
|
||||
1, 0,
|
||||
@"uiGrid child horizontal alignment center constraint");
|
||||
[self addConstraint:self.xcenterc];
|
||||
}
|
||||
if (self.halign == uiAlignEnd || self.halign == uiAlignFill) {
|
||||
self.trailingc = uiprivMkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiGrid child horizontal alignment end constraint");
|
||||
[self addConstraint:self.trailingc];
|
||||
}
|
||||
|
||||
if (self.valign == uiAlignStart || self.valign == uiAlignFill) {
|
||||
self.topc = uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiGrid child vertical alignment start constraint");
|
||||
[self addConstraint:self.topc];
|
||||
}
|
||||
if (self.valign == uiAlignCenter) {
|
||||
self.ycenterc = uiprivMkConstraint(self, NSLayoutAttributeCenterY,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeCenterY,
|
||||
1, 0,
|
||||
@"uiGrid child vertical alignment center constraint");
|
||||
[self addConstraint:self.ycenterc];
|
||||
}
|
||||
if (self.valign == uiAlignEnd || self.valign == uiAlignFill) {
|
||||
self.bottomc = uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
[self view], NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiGrid child vertical alignment end constraint");
|
||||
[self addConstraint:self.bottomc];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
if (self.leadingc != nil) {
|
||||
[self removeConstraint:self.leadingc];
|
||||
self.leadingc = nil;
|
||||
}
|
||||
if (self.topc != nil) {
|
||||
[self removeConstraint:self.topc];
|
||||
self.topc = nil;
|
||||
}
|
||||
if (self.trailingc != nil) {
|
||||
[self removeConstraint:self.trailingc];
|
||||
self.trailingc = nil;
|
||||
}
|
||||
if (self.bottomc != nil) {
|
||||
[self removeConstraint:self.bottomc];
|
||||
self.bottomc = nil;
|
||||
}
|
||||
if (self.xcenterc != nil) {
|
||||
[self removeConstraint:self.xcenterc];
|
||||
self.xcenterc = nil;
|
||||
}
|
||||
if (self.ycenterc != nil) {
|
||||
[self removeConstraint:self.ycenterc];
|
||||
self.ycenterc = nil;
|
||||
}
|
||||
|
||||
uiControlSetParent(self.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(self.c), nil);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
}
|
||||
|
||||
- (NSView *)view
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation gridView
|
||||
|
||||
- (id)initWithG:(uiGrid *)gg
|
||||
{
|
||||
self = [super initWithFrame:NSZeroRect];
|
||||
if (self != nil) {
|
||||
self->g = gg;
|
||||
self->padded = 0;
|
||||
self->children = [NSMutableArray new];
|
||||
|
||||
self->edges = [NSMutableArray new];
|
||||
self->inBetweens = [NSMutableArray new];
|
||||
|
||||
self->emptyCellViews = [NSMutableArray new];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)onDestroy
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
[self removeOurConstraints];
|
||||
[self->edges release];
|
||||
[self->inBetweens release];
|
||||
|
||||
[self->emptyCellViews release];
|
||||
|
||||
for (gc in self->children) {
|
||||
[gc onDestroy];
|
||||
uiControlDestroy(gc.c);
|
||||
[gc removeFromSuperview];
|
||||
}
|
||||
[self->children release];
|
||||
}
|
||||
|
||||
- (void)removeOurConstraints
|
||||
{
|
||||
NSView *v;
|
||||
|
||||
if ([self->edges count] != 0) {
|
||||
[self removeConstraints:self->edges];
|
||||
[self->edges removeAllObjects];
|
||||
}
|
||||
if ([self->inBetweens count] != 0) {
|
||||
[self removeConstraints:self->inBetweens];
|
||||
[self->inBetweens removeAllObjects];
|
||||
}
|
||||
|
||||
for (v in self->emptyCellViews)
|
||||
[v removeFromSuperview];
|
||||
[self->emptyCellViews removeAllObjects];
|
||||
}
|
||||
|
||||
- (void)syncEnableStates:(int)enabled
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
for (gc in self->children)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(gc.c), enabled);
|
||||
}
|
||||
|
||||
- (CGFloat)paddingAmount
|
||||
{
|
||||
if (!self->padded)
|
||||
return 0.0;
|
||||
return uiDarwinPaddingAmount(NULL);
|
||||
}
|
||||
|
||||
// LONGTERM stop early if all controls are hidden
|
||||
- (void)establishOurConstraints
|
||||
{
|
||||
gridChild *gc;
|
||||
CGFloat padding;
|
||||
int xmin, ymin;
|
||||
int xmax, ymax;
|
||||
int xcount, ycount;
|
||||
BOOL first;
|
||||
int **gg;
|
||||
NSView ***gv;
|
||||
BOOL **gspan;
|
||||
int x, y;
|
||||
int i;
|
||||
NSLayoutConstraint *c;
|
||||
int firstx, firsty;
|
||||
BOOL *hexpand, *vexpand;
|
||||
BOOL doit;
|
||||
BOOL onlyEmptyAndSpanning;
|
||||
|
||||
[self removeOurConstraints];
|
||||
if ([self->children count] == 0)
|
||||
return;
|
||||
padding = [self paddingAmount];
|
||||
|
||||
// first, figure out the minimum and maximum row and column numbers
|
||||
// ignore hidden controls
|
||||
first = YES;
|
||||
for (gc in self->children) {
|
||||
// this bit is important: it ensures row ymin and column xmin have at least one cell to draw, so the onlyEmptyAndSpanning logic below will never run on those rows
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (first) {
|
||||
xmin = gc.left;
|
||||
ymin = gc.top;
|
||||
xmax = gc.left + gc.xspan;
|
||||
ymax = gc.top + gc.yspan;
|
||||
first = NO;
|
||||
continue;
|
||||
}
|
||||
if (xmin > gc.left)
|
||||
xmin = gc.left;
|
||||
if (ymin > gc.top)
|
||||
ymin = gc.top;
|
||||
if (xmax < (gc.left + gc.xspan))
|
||||
xmax = gc.left + gc.xspan;
|
||||
if (ymax < (gc.top + gc.yspan))
|
||||
ymax = gc.top + gc.yspan;
|
||||
}
|
||||
if (first != NO) // the entire grid is hidden; do nothing
|
||||
return;
|
||||
xcount = xmax - xmin;
|
||||
ycount = ymax - ymin;
|
||||
|
||||
// now build a topological map of the grid gg[y][x]
|
||||
// also figure out which cells contain spanned views so they can be ignored later
|
||||
// treat hidden controls by keeping the indices -1
|
||||
gg = (int **) uiprivAlloc(ycount * sizeof (int *), "int[][]");
|
||||
gspan = (BOOL **) uiprivAlloc(ycount * sizeof (BOOL *), "BOOL[][]");
|
||||
for (y = 0; y < ycount; y++) {
|
||||
gg[y] = (int *) uiprivAlloc(xcount * sizeof (int), "int[]");
|
||||
gspan[y] = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]");
|
||||
for (x = 0; x < xcount; x++)
|
||||
gg[y][x] = -1; // empty
|
||||
}
|
||||
for (i = 0; i < [self->children count]; i++) {
|
||||
gc = (gridChild *) [self->children objectAtIndex:i];
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
for (y = gc.top; y < gc.top + gc.yspan; y++)
|
||||
for (x = gc.left; x < gc.left + gc.xspan; x++) {
|
||||
gg[y - ymin][x - xmin] = i;
|
||||
if (x != gc.left || y != gc.top)
|
||||
gspan[y - ymin][x - xmin] = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// if a row or column only contains emptys and spanning cells of a opposite-direction spannings, remove it by duplicating the previous row or column
|
||||
for (y = 0; y < ycount; y++) {
|
||||
onlyEmptyAndSpanning = YES;
|
||||
for (x = 0; x < xcount; x++)
|
||||
if (gg[y][x] != -1) {
|
||||
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
|
||||
if (gc.yspan == 1 || gc.top - ymin == y) {
|
||||
onlyEmptyAndSpanning = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onlyEmptyAndSpanning)
|
||||
for (x = 0; x < xcount; x++) {
|
||||
gg[y][x] = gg[y - 1][x];
|
||||
gspan[y][x] = YES;
|
||||
}
|
||||
}
|
||||
for (x = 0; x < xcount; x++) {
|
||||
onlyEmptyAndSpanning = YES;
|
||||
for (y = 0; y < ycount; y++)
|
||||
if (gg[y][x] != -1) {
|
||||
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
|
||||
if (gc.xspan == 1 || gc.left - xmin == x) {
|
||||
onlyEmptyAndSpanning = NO;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (onlyEmptyAndSpanning)
|
||||
for (y = 0; y < ycount; y++) {
|
||||
gg[y][x] = gg[y][x - 1];
|
||||
gspan[y][x] = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// now build a topological map of the grid's views gv[y][x]
|
||||
// for any empty cell, create a dummy view
|
||||
gv = (NSView ***) uiprivAlloc(ycount * sizeof (NSView **), "NSView *[][]");
|
||||
for (y = 0; y < ycount; y++) {
|
||||
gv[y] = (NSView **) uiprivAlloc(xcount * sizeof (NSView *), "NSView *[]");
|
||||
for (x = 0; x < xcount; x++)
|
||||
if (gg[y][x] == -1) {
|
||||
gv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect];
|
||||
[gv[y][x] setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:gv[y][x]];
|
||||
[self->emptyCellViews addObject:gv[y][x]];
|
||||
} else {
|
||||
gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
|
||||
gv[y][x] = gc;
|
||||
}
|
||||
}
|
||||
|
||||
// now figure out which rows and columns really expand
|
||||
hexpand = (BOOL *) uiprivAlloc(xcount * sizeof (BOOL), "BOOL[]");
|
||||
vexpand = (BOOL *) uiprivAlloc(ycount * sizeof (BOOL), "BOOL[]");
|
||||
// first, which don't span
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.hexpand && gc.xspan == 1)
|
||||
hexpand[gc.left - xmin] = YES;
|
||||
if (gc.vexpand && gc.yspan == 1)
|
||||
vexpand[gc.top - ymin] = YES;
|
||||
}
|
||||
// second, which do span
|
||||
// the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.hexpand && gc.xspan != 1) {
|
||||
doit = YES;
|
||||
for (x = gc.left; x < gc.left + gc.xspan; x++)
|
||||
if (hexpand[x - xmin]) {
|
||||
doit = NO;
|
||||
break;
|
||||
}
|
||||
if (doit)
|
||||
for (x = gc.left; x < gc.left + gc.xspan; x++)
|
||||
hexpand[x - xmin] = YES;
|
||||
}
|
||||
if (gc.vexpand && gc.yspan != 1) {
|
||||
doit = YES;
|
||||
for (y = gc.top; y < gc.top + gc.yspan; y++)
|
||||
if (vexpand[y - ymin]) {
|
||||
doit = NO;
|
||||
break;
|
||||
}
|
||||
if (doit)
|
||||
for (y = gc.top; y < gc.top + gc.yspan; y++)
|
||||
vexpand[y - ymin] = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// now establish all the edge constraints
|
||||
// leading and trailing edges
|
||||
for (y = 0; y < ycount; y++) {
|
||||
c = uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][0], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiGrid leading edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
c = uiprivMkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][xcount - 1], NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiGrid trailing edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
// top and bottom edges
|
||||
for (x = 0; x < xcount; x++) {
|
||||
c = uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
gv[0][x], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiGrid top edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
c = uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
gv[ycount - 1][x], NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiGrid bottom edge constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
|
||||
// now align leading and top edges
|
||||
// do NOT align spanning cells!
|
||||
for (x = 0; x < xcount; x++) {
|
||||
for (y = 0; y < ycount; y++)
|
||||
if (!gspan[y][x])
|
||||
break;
|
||||
firsty = y;
|
||||
for (y++; y < ycount; y++) {
|
||||
if (gspan[y][x])
|
||||
continue;
|
||||
c = uiprivMkConstraint(gv[firsty][x], NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiGrid column leading constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
}
|
||||
for (y = 0; y < ycount; y++) {
|
||||
for (x = 0; x < xcount; x++)
|
||||
if (!gspan[y][x])
|
||||
break;
|
||||
firstx = x;
|
||||
for (x++; x < xcount; x++) {
|
||||
if (gspan[y][x])
|
||||
continue;
|
||||
c = uiprivMkConstraint(gv[y][firstx], NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiGrid row top constraint");
|
||||
[self addConstraint:c];
|
||||
[self->edges addObject:c];
|
||||
}
|
||||
}
|
||||
|
||||
// now string adjacent views together
|
||||
for (y = 0; y < ycount; y++)
|
||||
for (x = 1; x < xcount; x++)
|
||||
if (gv[y][x - 1] != gv[y][x]) {
|
||||
c = uiprivMkConstraint(gv[y][x - 1], NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeLeading,
|
||||
1, -padding,
|
||||
@"uiGrid internal horizontal constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
}
|
||||
for (x = 0; x < xcount; x++)
|
||||
for (y = 1; y < ycount; y++)
|
||||
if (gv[y - 1][x] != gv[y][x]) {
|
||||
c = uiprivMkConstraint(gv[y - 1][x], NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
gv[y][x], NSLayoutAttributeTop,
|
||||
1, -padding,
|
||||
@"uiGrid internal vertical constraint");
|
||||
[self addConstraint:c];
|
||||
[self->inBetweens addObject:c];
|
||||
}
|
||||
|
||||
// now set priorities for all widgets that expand or not
|
||||
// if a cell is in an expanding row, OR If it spans, then it must be willing to stretch
|
||||
// otherwise, it tries not to
|
||||
// note we don't use NSLayoutPriorityRequired as that will cause things to squish when they shouldn't
|
||||
for (gc in self->children) {
|
||||
NSLayoutPriority priority;
|
||||
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (hexpand[gc.left - xmin] || gc.xspan != 1)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
priority = NSLayoutPriorityDefaultHigh;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationHorizontal);
|
||||
// same for vertical direction
|
||||
if (vexpand[gc.top - ymin] || gc.yspan != 1)
|
||||
priority = NSLayoutPriorityDefaultLow;
|
||||
else
|
||||
priority = NSLayoutPriorityDefaultHigh;
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationVertical);
|
||||
}
|
||||
|
||||
// TODO make all expanding rows/columns the same height/width
|
||||
|
||||
// and finally clean up
|
||||
uiprivFree(hexpand);
|
||||
uiprivFree(vexpand);
|
||||
for (y = 0; y < ycount; y++) {
|
||||
uiprivFree(gg[y]);
|
||||
uiprivFree(gv[y]);
|
||||
uiprivFree(gspan[y]);
|
||||
}
|
||||
uiprivFree(gg);
|
||||
uiprivFree(gv);
|
||||
uiprivFree(gspan);
|
||||
}
|
||||
|
||||
- (void)append:(gridChild *)gc
|
||||
{
|
||||
BOOL update;
|
||||
int oldnh, oldnv;
|
||||
|
||||
[gc setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:gc];
|
||||
|
||||
// no need to set priority here; that's done in establishOurConstraints
|
||||
|
||||
oldnh = [self nhexpand];
|
||||
oldnv = [self nvexpand];
|
||||
[self->children addObject:gc];
|
||||
|
||||
[self establishOurConstraints];
|
||||
update = NO;
|
||||
if (gc.hexpand)
|
||||
if (oldnh == 0)
|
||||
update = YES;
|
||||
if (gc.vexpand)
|
||||
if (oldnv == 0)
|
||||
update = YES;
|
||||
if (update)
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->g));
|
||||
|
||||
[gc release]; // we don't need the initial reference now
|
||||
}
|
||||
|
||||
- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at
|
||||
{
|
||||
gridChild *other;
|
||||
BOOL found;
|
||||
|
||||
found = NO;
|
||||
for (other in self->children)
|
||||
if (other.c == c) {
|
||||
found = YES;
|
||||
break;
|
||||
}
|
||||
if (!found)
|
||||
uiprivUserBug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g);
|
||||
|
||||
switch (at) {
|
||||
case uiAtLeading:
|
||||
gc.left = other.left - gc.xspan;
|
||||
gc.top = other.top;
|
||||
break;
|
||||
case uiAtTop:
|
||||
gc.left = other.left;
|
||||
gc.top = other.top - gc.yspan;
|
||||
break;
|
||||
case uiAtTrailing:
|
||||
gc.left = other.left + other.xspan;
|
||||
gc.top = other.top;
|
||||
break;
|
||||
case uiAtBottom:
|
||||
gc.left = other.left;
|
||||
gc.top = other.top + other.yspan;
|
||||
break;
|
||||
// TODO add error checks to ALL enums
|
||||
}
|
||||
|
||||
[self append:gc];
|
||||
}
|
||||
|
||||
- (int)isPadded
|
||||
{
|
||||
return self->padded;
|
||||
}
|
||||
|
||||
- (void)setPadded:(int)p
|
||||
{
|
||||
CGFloat padding;
|
||||
NSLayoutConstraint *c;
|
||||
|
||||
#if 0 /* TODO */
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),
|
||||
dispatch_get_main_queue(),
|
||||
^{ [[self window] visualizeConstraints:[self constraints]]; }
|
||||
);
|
||||
#endif
|
||||
self->padded = p;
|
||||
padding = [self paddingAmount];
|
||||
for (c in self->inBetweens)
|
||||
switch ([c firstAttribute]) {
|
||||
case NSLayoutAttributeLeading:
|
||||
case NSLayoutAttributeTop:
|
||||
[c setConstant:padding];
|
||||
break;
|
||||
case NSLayoutAttributeTrailing:
|
||||
case NSLayoutAttributeBottom:
|
||||
[c setConstant:-padding];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)hugsTrailing
|
||||
{
|
||||
// only hug if we have horizontally expanding
|
||||
return [self nhexpand] != 0;
|
||||
}
|
||||
|
||||
- (BOOL)hugsBottom
|
||||
{
|
||||
// only hug if we have vertically expanding
|
||||
return [self nvexpand] != 0;
|
||||
}
|
||||
|
||||
- (int)nhexpand
|
||||
{
|
||||
gridChild *gc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.hexpand)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
- (int)nvexpand
|
||||
{
|
||||
gridChild *gc;
|
||||
int n;
|
||||
|
||||
n = 0;
|
||||
for (gc in self->children) {
|
||||
if (!uiControlVisible(gc.c))
|
||||
continue;
|
||||
if (gc.vexpand)
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiGridDestroy(uiControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
[g->view onDestroy];
|
||||
[g->view release];
|
||||
uiFreeControl(uiControl(g));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiGrid, view)
|
||||
uiDarwinControlDefaultParent(uiGrid, view)
|
||||
uiDarwinControlDefaultSetParent(uiGrid, view)
|
||||
uiDarwinControlDefaultToplevel(uiGrid, view)
|
||||
uiDarwinControlDefaultVisible(uiGrid, view)
|
||||
uiDarwinControlDefaultShow(uiGrid, view)
|
||||
uiDarwinControlDefaultHide(uiGrid, view)
|
||||
uiDarwinControlDefaultEnabled(uiGrid, view)
|
||||
uiDarwinControlDefaultEnable(uiGrid, view)
|
||||
uiDarwinControlDefaultDisable(uiGrid, view)
|
||||
|
||||
static void uiGridSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
|
||||
return;
|
||||
[g->view syncEnableStates:enabled];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiGrid, view)
|
||||
|
||||
static BOOL uiGridHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
return [g->view hugsTrailing];
|
||||
}
|
||||
|
||||
static BOOL uiGridHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
return [g->view hugsBottom];
|
||||
}
|
||||
|
||||
static void uiGridChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
[g->view establishOurConstraints];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHuggingPriority(uiGrid, view)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiGrid, view)
|
||||
|
||||
static void uiGridChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGrid *g = uiGrid(c);
|
||||
|
||||
[g->view establishOurConstraints];
|
||||
}
|
||||
|
||||
static gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign, uiGrid *g)
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
if (xspan < 0)
|
||||
uiprivUserBug("You cannot have a negative xspan in a uiGrid cell.");
|
||||
if (yspan < 0)
|
||||
uiprivUserBug("You cannot have a negative yspan in a uiGrid cell.");
|
||||
gc = [gridChild new];
|
||||
gc.xspan = xspan;
|
||||
gc.yspan = yspan;
|
||||
gc.hexpand = hexpand;
|
||||
gc.halign = halign;
|
||||
gc.vexpand = vexpand;
|
||||
gc.valign = valign;
|
||||
[gc setC:c grid:g];
|
||||
return gc;
|
||||
}
|
||||
|
||||
void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
// LONGTERM on other platforms
|
||||
// or at leat allow this and implicitly turn it into a spacer
|
||||
if (c == NULL)
|
||||
uiprivUserBug("You cannot add NULL to a uiGrid.");
|
||||
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
|
||||
gc.left = left;
|
||||
gc.top = top;
|
||||
[g->view append:gc];
|
||||
}
|
||||
|
||||
void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
|
||||
{
|
||||
gridChild *gc;
|
||||
|
||||
gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
|
||||
[g->view insert:gc after:existing at:at];
|
||||
}
|
||||
|
||||
int uiGridPadded(uiGrid *g)
|
||||
{
|
||||
return [g->view isPadded];
|
||||
}
|
||||
|
||||
void uiGridSetPadded(uiGrid *g, int padded)
|
||||
{
|
||||
[g->view setPadded:padded];
|
||||
}
|
||||
|
||||
uiGrid *uiNewGrid(void)
|
||||
{
|
||||
uiGrid *g;
|
||||
|
||||
uiDarwinNewControl(uiGrid, g);
|
||||
|
||||
g->view = [[gridView alloc] initWithG:g];
|
||||
|
||||
return g;
|
||||
}
|
||||
194
dep/libui/darwin/group.m
Normal file
194
dep/libui/darwin/group.m
Normal file
@@ -0,0 +1,194 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiGroup {
|
||||
uiDarwinControl c;
|
||||
NSBox *box;
|
||||
uiControl *child;
|
||||
NSLayoutPriority oldHorzHuggingPri;
|
||||
NSLayoutPriority oldVertHuggingPri;
|
||||
int margined;
|
||||
uiprivSingleChildConstraints constraints;
|
||||
NSLayoutPriority horzHuggingPri;
|
||||
NSLayoutPriority vertHuggingPri;
|
||||
};
|
||||
|
||||
static void removeConstraints(uiGroup *g)
|
||||
{
|
||||
// set to contentView instead of to the box itself, otherwise we get clipping underneath the label
|
||||
uiprivSingleChildConstraintsRemove(&(g->constraints), [g->box contentView]);
|
||||
}
|
||||
|
||||
static void uiGroupDestroy(uiControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
removeConstraints(g);
|
||||
if (g->child != NULL) {
|
||||
uiControlSetParent(g->child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);
|
||||
uiControlDestroy(g->child);
|
||||
}
|
||||
[g->box release];
|
||||
uiFreeControl(uiControl(g));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiGroup, box)
|
||||
uiDarwinControlDefaultParent(uiGroup, box)
|
||||
uiDarwinControlDefaultSetParent(uiGroup, box)
|
||||
uiDarwinControlDefaultToplevel(uiGroup, box)
|
||||
uiDarwinControlDefaultVisible(uiGroup, box)
|
||||
uiDarwinControlDefaultShow(uiGroup, box)
|
||||
uiDarwinControlDefaultHide(uiGroup, box)
|
||||
uiDarwinControlDefaultEnabled(uiGroup, box)
|
||||
uiDarwinControlDefaultEnable(uiGroup, box)
|
||||
uiDarwinControlDefaultDisable(uiGroup, box)
|
||||
|
||||
static void uiGroupSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
|
||||
return;
|
||||
if (g->child != NULL)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(g->child), enabled);
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiGroup, box)
|
||||
|
||||
static void groupRelayout(uiGroup *g)
|
||||
{
|
||||
NSView *childView;
|
||||
|
||||
removeConstraints(g);
|
||||
if (g->child == NULL)
|
||||
return;
|
||||
childView = (NSView *) uiControlHandle(g->child);
|
||||
uiprivSingleChildConstraintsEstablish(&(g->constraints),
|
||||
[g->box contentView], childView,
|
||||
uiDarwinControlHugsTrailingEdge(uiDarwinControl(g->child)),
|
||||
uiDarwinControlHugsBottom(uiDarwinControl(g->child)),
|
||||
g->margined,
|
||||
@"uiGroup");
|
||||
// needed for some very rare drawing errors...
|
||||
uiprivJiggleViewLayout(g->box);
|
||||
}
|
||||
|
||||
// TODO rename these since I'm starting to get confused by what they mean by hugging
|
||||
BOOL uiGroupHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
// TODO make a function?
|
||||
return g->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
BOOL uiGroupHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
return g->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
static void uiGroupChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
groupRelayout(g);
|
||||
}
|
||||
|
||||
static NSLayoutPriority uiGroupHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
return g->horzHuggingPri;
|
||||
return g->vertHuggingPri;
|
||||
}
|
||||
|
||||
static void uiGroupSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
g->horzHuggingPri = priority;
|
||||
else
|
||||
g->vertHuggingPri = priority;
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(g));
|
||||
}
|
||||
|
||||
static void uiGroupChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiGroup *g = uiGroup(c);
|
||||
|
||||
groupRelayout(g);
|
||||
}
|
||||
|
||||
char *uiGroupTitle(uiGroup *g)
|
||||
{
|
||||
return uiDarwinNSStringToText([g->box title]);
|
||||
}
|
||||
|
||||
void uiGroupSetTitle(uiGroup *g, const char *title)
|
||||
{
|
||||
[g->box setTitle:uiprivToNSString(title)];
|
||||
}
|
||||
|
||||
void uiGroupSetChild(uiGroup *g, uiControl *child)
|
||||
{
|
||||
NSView *childView;
|
||||
|
||||
if (g->child != NULL) {
|
||||
removeConstraints(g);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
uiControlSetParent(g->child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);
|
||||
}
|
||||
g->child = child;
|
||||
if (g->child != NULL) {
|
||||
childView = (NSView *) uiControlHandle(g->child);
|
||||
uiControlSetParent(g->child, uiControl(g));
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(g->child), [g->box contentView]);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(g->child), uiControlEnabledToUser(uiControl(g)));
|
||||
// don't hug, just in case we're a stretchy group
|
||||
g->oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationHorizontal);
|
||||
g->oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationVertical);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
|
||||
}
|
||||
groupRelayout(g);
|
||||
}
|
||||
|
||||
int uiGroupMargined(uiGroup *g)
|
||||
{
|
||||
return g->margined;
|
||||
}
|
||||
|
||||
void uiGroupSetMargined(uiGroup *g, int margined)
|
||||
{
|
||||
g->margined = margined;
|
||||
uiprivSingleChildConstraintsSetMargined(&(g->constraints), g->margined);
|
||||
}
|
||||
|
||||
uiGroup *uiNewGroup(const char *title)
|
||||
{
|
||||
uiGroup *g;
|
||||
|
||||
uiDarwinNewControl(uiGroup, g);
|
||||
|
||||
g->box = [[NSBox alloc] initWithFrame:NSZeroRect];
|
||||
[g->box setTitle:uiprivToNSString(title)];
|
||||
[g->box setBoxType:NSBoxPrimary];
|
||||
[g->box setBorderType:NSLineBorder];
|
||||
[g->box setTransparent:NO];
|
||||
[g->box setTitlePosition:NSAtTop];
|
||||
// we can't use uiDarwinSetControlFont() because the selector is different
|
||||
[g->box setTitleFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
|
||||
|
||||
// default to low hugging to not hug edges
|
||||
g->horzHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
g->vertHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
|
||||
return g;
|
||||
}
|
||||
84
dep/libui/darwin/image.m
Normal file
84
dep/libui/darwin/image.m
Normal file
@@ -0,0 +1,84 @@
|
||||
// 25 june 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiImage {
|
||||
NSImage *i;
|
||||
NSSize size;
|
||||
};
|
||||
|
||||
uiImage *uiNewImage(double width, double height)
|
||||
{
|
||||
uiImage *i;
|
||||
|
||||
i = uiprivNew(uiImage);
|
||||
i->size = NSMakeSize(width, height);
|
||||
i->i = [[NSImage alloc] initWithSize:i->size];
|
||||
return i;
|
||||
}
|
||||
|
||||
void uiFreeImage(uiImage *i)
|
||||
{
|
||||
[i->i release];
|
||||
uiprivFree(i);
|
||||
}
|
||||
|
||||
void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int byteStride)
|
||||
{
|
||||
NSBitmapImageRep *repCalibrated, *repsRGB;
|
||||
int x, y;
|
||||
uint8_t *pix, *data;
|
||||
NSInteger realStride;
|
||||
|
||||
repCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:pixelWidth
|
||||
pixelsHigh:pixelHeight
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:4
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:0
|
||||
bytesPerRow:0
|
||||
bitsPerPixel:32];
|
||||
|
||||
// Apple doesn't explicitly document this, but we apparently need to use native system endian for the data :|
|
||||
// TODO split this into a utility routine?
|
||||
// TODO find proper documentation
|
||||
// TODO test this on a big-endian system somehow; I have a feeling the above comment is wrong about the diagnosis since the order we are specifying is now 0xAABBGGRR
|
||||
pix = (uint8_t *) pixels;
|
||||
data = (uint8_t *) [repCalibrated bitmapData];
|
||||
realStride = [repCalibrated bytesPerRow];
|
||||
for (y = 0; y < pixelHeight; y++) {
|
||||
for (x = 0; x < pixelWidth * 4; x += 4) {
|
||||
union {
|
||||
uint32_t v32;
|
||||
uint8_t v8[4];
|
||||
} v;
|
||||
|
||||
v.v32 = ((uint32_t) (pix[x + 3])) << 24;
|
||||
v.v32 |= ((uint32_t) (pix[x + 2])) << 16;
|
||||
v.v32 |= ((uint32_t) (pix[x + 1])) << 8;
|
||||
v.v32 |= ((uint32_t) (pix[x]));
|
||||
data[x] = v.v8[0];
|
||||
data[x + 1] = v.v8[1];
|
||||
data[x + 2] = v.v8[2];
|
||||
data[x + 3] = v.v8[3];
|
||||
}
|
||||
pix += byteStride;
|
||||
data += realStride;
|
||||
}
|
||||
|
||||
// we can't call the constructor with this, but we can retag (NOT convert)
|
||||
repsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
|
||||
[i->i addRepresentation:repsRGB];
|
||||
[repsRGB setSize:i->size];
|
||||
// don't release repsRGB; it may be equivalent to repCalibrated
|
||||
// do release repCalibrated though; NSImage has a ref to either it or to repsRGB
|
||||
[repCalibrated release];
|
||||
}
|
||||
|
||||
NSImage *uiprivImageNSImage(uiImage *i)
|
||||
{
|
||||
return i->i;
|
||||
}
|
||||
43
dep/libui/darwin/label.m
Normal file
43
dep/libui/darwin/label.m
Normal file
@@ -0,0 +1,43 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
struct uiLabel {
|
||||
uiDarwinControl c;
|
||||
NSTextField *textfield;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiLabel, textfield)
|
||||
|
||||
char *uiLabelText(uiLabel *l)
|
||||
{
|
||||
return uiDarwinNSStringToText([l->textfield stringValue]);
|
||||
}
|
||||
|
||||
void uiLabelSetText(uiLabel *l, const char *text)
|
||||
{
|
||||
[l->textfield setStringValue:uiprivToNSString(text)];
|
||||
}
|
||||
|
||||
NSTextField *uiprivNewLabel(NSString *str)
|
||||
{
|
||||
NSTextField *tf;
|
||||
|
||||
tf = [[NSTextField alloc] initWithFrame:NSZeroRect];
|
||||
[tf setStringValue:str];
|
||||
[tf setEditable:NO];
|
||||
[tf setSelectable:NO];
|
||||
[tf setDrawsBackground:NO];
|
||||
uiprivFinishNewTextField(tf, NO);
|
||||
return tf;
|
||||
}
|
||||
|
||||
uiLabel *uiNewLabel(const char *text)
|
||||
{
|
||||
uiLabel *l;
|
||||
|
||||
uiDarwinNewControl(uiLabel, l);
|
||||
|
||||
l->textfield = uiprivNewLabel(uiprivToNSString(text));
|
||||
|
||||
return l;
|
||||
}
|
||||
288
dep/libui/darwin/main.m
Normal file
288
dep/libui/darwin/main.m
Normal file
@@ -0,0 +1,288 @@
|
||||
// 6 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
static BOOL canQuit = NO;
|
||||
static NSAutoreleasePool *globalPool;
|
||||
static uiprivApplicationClass *app;
|
||||
static uiprivAppDelegate *delegate;
|
||||
|
||||
static BOOL (^isRunning)(void);
|
||||
static BOOL stepsIsRunning;
|
||||
|
||||
@implementation uiprivApplicationClass
|
||||
|
||||
- (void)sendEvent:(NSEvent *)e
|
||||
{
|
||||
if (uiprivSendAreaEvents(e) != 0)
|
||||
return;
|
||||
[super sendEvent:e];
|
||||
}
|
||||
|
||||
// NSColorPanel always sends changeColor: to the first responder regardless of whether there's a target set on it
|
||||
// we can override it here (see colorbutton.m)
|
||||
// thanks to mikeash in irc.freenode.net/#macdev for informing me this is how the first responder chain is initiated
|
||||
// it turns out NSFontManager also sends changeFont: through this; let's inhibit that here too (see fontbutton.m)
|
||||
- (BOOL)sendAction:(SEL)sel to:(id)to from:(id)from
|
||||
{
|
||||
if (uiprivColorButtonInhibitSendAction(sel, from, to))
|
||||
return NO;
|
||||
if (uiprivFontButtonInhibitSendAction(sel, from, to))
|
||||
return NO;
|
||||
return [super sendAction:sel to:to from:from];
|
||||
}
|
||||
|
||||
// likewise, NSFontManager also sends NSFontPanelValidation messages to the first responder, however it does NOT use sendAction:from:to:!
|
||||
// instead, it uses this one (thanks swillits in irc.freenode.net/#macdev)
|
||||
// we also need to override it (see fontbutton.m)
|
||||
- (id)targetForAction:(SEL)sel to:(id)to from:(id)from
|
||||
{
|
||||
id override;
|
||||
|
||||
if (uiprivFontButtonOverrideTargetForAction(sel, from, to, &override))
|
||||
return override;
|
||||
return [super targetForAction:sel to:to from:from];
|
||||
}
|
||||
|
||||
// hey look! we're overriding terminate:!
|
||||
// we're going to make sure we can go back to main() whether Cocoa likes it or not!
|
||||
// and just how are we going to do that, hm?
|
||||
// (note: this is called after applicationShouldTerminate:)
|
||||
- (void)terminate:(id)sender
|
||||
{
|
||||
// yes that's right folks: DO ABSOLUTELY NOTHING.
|
||||
// the magic is [NSApp run] will just... stop.
|
||||
|
||||
// well let's not do nothing; let's actually quit our graceful way
|
||||
NSEvent *e;
|
||||
|
||||
if (!canQuit)
|
||||
uiprivImplBug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs");
|
||||
|
||||
[uiprivNSApp() stop:uiprivNSApp()];
|
||||
// stop: won't register until another event has passed; let's synthesize one
|
||||
e = [NSEvent otherEventWithType:NSApplicationDefined
|
||||
location:NSZeroPoint
|
||||
modifierFlags:0
|
||||
timestamp:[[NSProcessInfo processInfo] systemUptime]
|
||||
windowNumber:0
|
||||
context:[NSGraphicsContext currentContext]
|
||||
subtype:0
|
||||
data1:0
|
||||
data2:0];
|
||||
[uiprivNSApp() postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO)
|
||||
|
||||
// and in case uiMainSteps() was called
|
||||
stepsIsRunning = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation uiprivAppDelegate
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
// Apple docs: "Don't Use Accessor Methods in Initializer Methods and dealloc"
|
||||
[_menuManager release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
|
||||
{
|
||||
// for debugging
|
||||
NSLog(@"in applicationShouldTerminate:");
|
||||
if (uiprivShouldQuit()) {
|
||||
canQuit = YES;
|
||||
// this will call terminate:, which is the same as uiQuit()
|
||||
return NSTerminateNow;
|
||||
}
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiInitOptions uiprivOptions;
|
||||
|
||||
const char *uiInit(uiInitOptions *o)
|
||||
{
|
||||
@autoreleasepool {
|
||||
uiprivOptions = *o;
|
||||
app = [[uiprivApplicationClass sharedApplication] retain];
|
||||
// don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy!
|
||||
// see https://github.com/andlabs/ui/issues/6
|
||||
[uiprivNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
delegate = [uiprivAppDelegate new];
|
||||
[uiprivNSApp() setDelegate:delegate];
|
||||
|
||||
uiprivInitAlloc();
|
||||
uiprivLoadFutures();
|
||||
uiprivLoadUndocumented();
|
||||
|
||||
// always do this so we always have an application menu
|
||||
uiprivAppDelegate().menuManager = [[uiprivMenuManager new] autorelease];
|
||||
[uiprivNSApp() setMainMenu:[uiprivAppDelegate().menuManager makeMenubar]];
|
||||
|
||||
uiprivSetupFontPanel();
|
||||
|
||||
uiprivInitUnderlineColors();
|
||||
}
|
||||
|
||||
globalPool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uiUninit(void)
|
||||
{
|
||||
if (!globalPool)
|
||||
uiprivUserBug("You must call uiInit() first!");
|
||||
[globalPool release];
|
||||
|
||||
@autoreleasepool {
|
||||
uiprivUninitUnderlineColors();
|
||||
[delegate release];
|
||||
[uiprivNSApp() setDelegate:nil];
|
||||
[app release];
|
||||
uiprivUninitAlloc();
|
||||
}
|
||||
}
|
||||
|
||||
void uiFreeInitError(const char *err)
|
||||
{
|
||||
}
|
||||
|
||||
void uiMain(void)
|
||||
{
|
||||
isRunning = ^{
|
||||
return [uiprivNSApp() isRunning];
|
||||
};
|
||||
[uiprivNSApp() run];
|
||||
}
|
||||
|
||||
void uiMainSteps(void)
|
||||
{
|
||||
// SDL does this and it seems to be necessary for the menubar to work (see #182)
|
||||
[uiprivNSApp() finishLaunching];
|
||||
isRunning = ^{
|
||||
return stepsIsRunning;
|
||||
};
|
||||
stepsIsRunning = YES;
|
||||
}
|
||||
|
||||
int uiMainStep(int wait)
|
||||
{
|
||||
uiprivNextEventArgs nea;
|
||||
|
||||
nea.mask = NSAnyEventMask;
|
||||
|
||||
// ProPuke did this in his original PR requesting this
|
||||
// I'm not sure if this will work, but I assume it will...
|
||||
nea.duration = [NSDate distantPast];
|
||||
if (wait) // but this is normal so it will work
|
||||
nea.duration = [NSDate distantFuture];
|
||||
|
||||
nea.mode = NSDefaultRunLoopMode;
|
||||
nea.dequeue = YES;
|
||||
|
||||
return uiprivMainStep(&nea, ^(NSEvent *e) {
|
||||
return NO;
|
||||
});
|
||||
}
|
||||
|
||||
// see also:
|
||||
// - http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
|
||||
// - https://github.com/gnustep/gui/blob/master/Source/NSApplication.m
|
||||
int uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *e))
|
||||
{
|
||||
NSDate *expire;
|
||||
NSEvent *e;
|
||||
NSEventType type;
|
||||
|
||||
@autoreleasepool {
|
||||
if (!isRunning())
|
||||
return 0;
|
||||
|
||||
e = [uiprivNSApp() nextEventMatchingMask:nea->mask
|
||||
untilDate:nea->duration
|
||||
inMode:nea->mode
|
||||
dequeue:nea->dequeue];
|
||||
if (e == nil)
|
||||
return 1;
|
||||
|
||||
type = [e type];
|
||||
if (!interceptEvent(e))
|
||||
[uiprivNSApp() sendEvent:e];
|
||||
[uiprivNSApp() updateWindows];
|
||||
|
||||
// GNUstep does this
|
||||
// it also updates the Services menu but there doesn't seem to be a public API for that so
|
||||
if (type != NSPeriodic && type != NSMouseMoved)
|
||||
[[uiprivNSApp() mainMenu] update];
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void uiQuit(void)
|
||||
{
|
||||
canQuit = YES;
|
||||
[uiprivNSApp() terminate:uiprivNSApp()];
|
||||
}
|
||||
|
||||
// thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch for this
|
||||
// LONGTERM will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()?
|
||||
void uiQueueMain(void (*f)(void *data), void *data)
|
||||
{
|
||||
// dispatch_get_main_queue() is a serial queue so it will not execute multiple uiQueueMain() functions concurrently
|
||||
// the signature of f matches dispatch_function_t
|
||||
dispatch_async_f(dispatch_get_main_queue(), data, f);
|
||||
}
|
||||
|
||||
@interface uiprivTimerDelegate : NSObject {
|
||||
int (*f)(void *data);
|
||||
void *data;
|
||||
}
|
||||
- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData;
|
||||
- (void)doTimer:(NSTimer *)timer;
|
||||
@end
|
||||
|
||||
@implementation uiprivTimerDelegate
|
||||
|
||||
- (id)initWithCallback:(int (*)(void *))callback data:(void *)callbackData
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->f = callback;
|
||||
self->data = callbackData;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)doTimer:(NSTimer *)timer
|
||||
{
|
||||
if (!(*(self->f))(self->data))
|
||||
[timer invalidate];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void uiTimer(int milliseconds, int (*f)(void *data), void *data)
|
||||
{
|
||||
uiprivTimerDelegate *delegate;
|
||||
|
||||
delegate = [[uiprivTimerDelegate alloc] initWithCallback:f data:data];
|
||||
[NSTimer scheduledTimerWithTimeInterval:(milliseconds / 1000.0)
|
||||
target:delegate
|
||||
selector:@selector(doTimer:)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
[delegate release];
|
||||
}
|
||||
|
||||
// TODO figure out the best way to clean the above up in uiUninit(), if it's even necessary
|
||||
// TODO that means figure out if timers can still fire without the main loop
|
||||
61
dep/libui/darwin/map.m
Normal file
61
dep/libui/darwin/map.m
Normal file
@@ -0,0 +1,61 @@
|
||||
// 17 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// unfortunately NSMutableDictionary copies its keys, meaning we can't use it for pointers
|
||||
// hence, this file
|
||||
// we could expose a NSMapTable directly, but let's treat all pointers as opaque and hide the implementation, just to be safe and prevent even more rewrites later
|
||||
struct uiprivMap {
|
||||
NSMapTable *m;
|
||||
};
|
||||
|
||||
uiprivMap *uiprivNewMap(void)
|
||||
{
|
||||
uiprivMap *m;
|
||||
|
||||
m = uiprivNew(uiprivMap);
|
||||
m->m = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
|
||||
valueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
|
||||
capacity:0];
|
||||
return m;
|
||||
}
|
||||
|
||||
void uiprivMapDestroy(uiprivMap *m)
|
||||
{
|
||||
if ([m->m count] != 0)
|
||||
uiprivImplBug("attempt to destroy map with items inside");
|
||||
[m->m release];
|
||||
uiprivFree(m);
|
||||
}
|
||||
|
||||
void *uiprivMapGet(uiprivMap *m, void *key)
|
||||
{
|
||||
return NSMapGet(m->m, key);
|
||||
}
|
||||
|
||||
void uiprivMapSet(uiprivMap *m, void *key, void *value)
|
||||
{
|
||||
NSMapInsert(m->m, key, value);
|
||||
}
|
||||
|
||||
void uiprivMapDelete(uiprivMap *m, void *key)
|
||||
{
|
||||
NSMapRemove(m->m, key);
|
||||
}
|
||||
|
||||
void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value))
|
||||
{
|
||||
NSMapEnumerator e;
|
||||
void *k, *v;
|
||||
|
||||
e = NSEnumerateMapTable(m->m);
|
||||
k = NULL;
|
||||
v = NULL;
|
||||
while (NSNextMapEnumeratorPair(&e, &k, &v))
|
||||
f(k, v);
|
||||
NSEndMapTableEnumeration(&e);
|
||||
}
|
||||
|
||||
void uiprivMapReset(uiprivMap *m)
|
||||
{
|
||||
NSResetMapTable(m->m);
|
||||
}
|
||||
368
dep/libui/darwin/menu.m
Normal file
368
dep/libui/darwin/menu.m
Normal file
@@ -0,0 +1,368 @@
|
||||
// 28 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
static NSMutableArray *menus = nil;
|
||||
static BOOL menusFinalized = NO;
|
||||
|
||||
struct uiMenu {
|
||||
NSMenu *menu;
|
||||
NSMenuItem *item;
|
||||
NSMutableArray *items;
|
||||
};
|
||||
|
||||
struct uiMenuItem {
|
||||
NSMenuItem *item;
|
||||
int type;
|
||||
BOOL disabled;
|
||||
void (*onClicked)(uiMenuItem *, uiWindow *, void *);
|
||||
void *onClickedData;
|
||||
};
|
||||
|
||||
enum {
|
||||
typeRegular,
|
||||
typeCheckbox,
|
||||
typeQuit,
|
||||
typePreferences,
|
||||
typeAbout,
|
||||
typeSeparator,
|
||||
};
|
||||
|
||||
static void mapItemReleaser(void *key, void *value)
|
||||
{
|
||||
uiMenuItem *item;
|
||||
|
||||
item = (uiMenuItem *) value;
|
||||
[item->item release];
|
||||
}
|
||||
|
||||
@implementation uiprivMenuManager
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->items = uiprivNewMap();
|
||||
self->hasQuit = NO;
|
||||
self->hasPreferences = NO;
|
||||
self->hasAbout = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapWalk(self->items, mapItemReleaser);
|
||||
uiprivMapReset(self->items);
|
||||
uiprivMapDestroy(self->items);
|
||||
uiprivUninitMenus();
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onClicked:(id)sender
|
||||
{
|
||||
uiMenuItem *item;
|
||||
|
||||
item = (uiMenuItem *) uiprivMapGet(self->items, sender);
|
||||
if (item->type == typeCheckbox)
|
||||
uiMenuItemSetChecked(item, !uiMenuItemChecked(item));
|
||||
// use the key window as the source of the menu event; it's the active window
|
||||
(*(item->onClicked))(item, uiprivWindowFromNSWindow([uiprivNSApp() keyWindow]), item->onClickedData);
|
||||
}
|
||||
|
||||
- (IBAction)onQuitClicked:(id)sender
|
||||
{
|
||||
if (uiprivShouldQuit())
|
||||
uiQuit();
|
||||
}
|
||||
|
||||
- (void)register:(NSMenuItem *)item to:(uiMenuItem *)smi
|
||||
{
|
||||
switch (smi->type) {
|
||||
case typeQuit:
|
||||
if (self->hasQuit)
|
||||
uiprivUserBug("You can't have multiple Quit menu items in one program.");
|
||||
self->hasQuit = YES;
|
||||
break;
|
||||
case typePreferences:
|
||||
if (self->hasPreferences)
|
||||
uiprivUserBug("You can't have multiple Preferences menu items in one program.");
|
||||
self->hasPreferences = YES;
|
||||
break;
|
||||
case typeAbout:
|
||||
if (self->hasAbout)
|
||||
uiprivUserBug("You can't have multiple About menu items in one program.");
|
||||
self->hasAbout = YES;
|
||||
break;
|
||||
}
|
||||
uiprivMapSet(self->items, item, smi);
|
||||
}
|
||||
|
||||
// on OS X there are two ways to handle menu items being enabled or disabled: automatically and manually
|
||||
// unfortunately, the application menu requires automatic menu handling for the Hide, Hide Others, and Show All items to work correctly
|
||||
// therefore, we have to handle enabling of the other options ourselves
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
||||
{
|
||||
uiMenuItem *smi;
|
||||
|
||||
// disable the special items if they aren't present
|
||||
if (item == self.quitItem && !self->hasQuit)
|
||||
return NO;
|
||||
if (item == self.preferencesItem && !self->hasPreferences)
|
||||
return NO;
|
||||
if (item == self.aboutItem && !self->hasAbout)
|
||||
return NO;
|
||||
// then poll the item's enabled/disabled state
|
||||
smi = (uiMenuItem *) uiprivMapGet(self->items, item);
|
||||
return !smi->disabled;
|
||||
}
|
||||
|
||||
// Cocoa constructs the default application menu by hand for each program; that's what MainMenu.[nx]ib does
|
||||
- (void)buildApplicationMenu:(NSMenu *)menubar
|
||||
{
|
||||
NSString *appName;
|
||||
NSMenuItem *appMenuItem;
|
||||
NSMenu *appMenu;
|
||||
NSMenuItem *item;
|
||||
NSString *title;
|
||||
NSMenu *servicesMenu;
|
||||
|
||||
// note: no need to call setAppleMenu: on this anymore; see https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_6Notes
|
||||
appName = [[NSProcessInfo processInfo] processName];
|
||||
appMenuItem = [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""] autorelease];
|
||||
appMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease];
|
||||
[appMenuItem setSubmenu:appMenu];
|
||||
[menubar addItem:appMenuItem];
|
||||
|
||||
// first is About
|
||||
title = [@"About " stringByAppendingString:appName];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""] autorelease];
|
||||
[item setTarget:self];
|
||||
[appMenu addItem:item];
|
||||
self.aboutItem = item;
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// next is Preferences
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","] autorelease];
|
||||
[item setTarget:self];
|
||||
[appMenu addItem:item];
|
||||
self.preferencesItem = item;
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// next is Services
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""] autorelease];
|
||||
servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
|
||||
[item setSubmenu:servicesMenu];
|
||||
[uiprivNSApp() setServicesMenu:servicesMenu];
|
||||
[appMenu addItem:item];
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// next are the three hiding options
|
||||
title = [@"Hide " stringByAppendingString:appName];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"] autorelease];
|
||||
// the .xib file says they go to -1 ("First Responder", which sounds wrong...)
|
||||
// to do that, we simply leave the target as nil
|
||||
[appMenu addItem:item];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] autorelease];
|
||||
[item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
|
||||
[appMenu addItem:item];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""] autorelease];
|
||||
[appMenu addItem:item];
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
// and finally Quit
|
||||
// DON'T use @selector(terminate:) as the action; we handle termination ourselves
|
||||
title = [@"Quit " stringByAppendingString:appName];
|
||||
item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@"q"] autorelease];
|
||||
[item setTarget:self];
|
||||
[appMenu addItem:item];
|
||||
self.quitItem = item;
|
||||
}
|
||||
|
||||
- (NSMenu *)makeMenubar
|
||||
{
|
||||
NSMenu *menubar;
|
||||
|
||||
menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||
[self buildApplicationMenu:menubar];
|
||||
return menubar;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void uiMenuItemEnable(uiMenuItem *item)
|
||||
{
|
||||
item->disabled = NO;
|
||||
// we don't need to explicitly update the menus here; they'll be updated the next time they're opened (thanks mikeash in irc.freenode.net/#macdev)
|
||||
}
|
||||
|
||||
void uiMenuItemDisable(uiMenuItem *item)
|
||||
{
|
||||
item->disabled = YES;
|
||||
}
|
||||
|
||||
void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
|
||||
{
|
||||
if (item->type == typeQuit)
|
||||
uiprivUserBug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
|
||||
item->onClicked = f;
|
||||
item->onClickedData = data;
|
||||
}
|
||||
|
||||
int uiMenuItemChecked(uiMenuItem *item)
|
||||
{
|
||||
return [item->item state] != NSOffState;
|
||||
}
|
||||
|
||||
void uiMenuItemSetChecked(uiMenuItem *item, int checked)
|
||||
{
|
||||
NSInteger state;
|
||||
|
||||
state = NSOffState;
|
||||
if ([item->item state] == NSOffState)
|
||||
state = NSOnState;
|
||||
[item->item setState:state];
|
||||
}
|
||||
|
||||
static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
uiMenuItem *item;
|
||||
|
||||
if (menusFinalized)
|
||||
uiprivUserBug("You can't create a new menu item after menus have been finalized.");
|
||||
|
||||
item = uiprivNew(uiMenuItem);
|
||||
|
||||
item->type = type;
|
||||
switch (item->type) {
|
||||
case typeQuit:
|
||||
item->item = [uiprivAppDelegate().menuManager.quitItem retain];
|
||||
break;
|
||||
case typePreferences:
|
||||
item->item = [uiprivAppDelegate().menuManager.preferencesItem retain];
|
||||
break;
|
||||
case typeAbout:
|
||||
item->item = [uiprivAppDelegate().menuManager.aboutItem retain];
|
||||
break;
|
||||
case typeSeparator:
|
||||
item->item = [[NSMenuItem separatorItem] retain];
|
||||
[m->menu addItem:item->item];
|
||||
break;
|
||||
default:
|
||||
item->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:@selector(onClicked:) keyEquivalent:@""];
|
||||
[item->item setTarget:uiprivAppDelegate().menuManager];
|
||||
[m->menu addItem:item->item];
|
||||
break;
|
||||
}
|
||||
|
||||
[uiprivAppDelegate().menuManager register:item->item to:item];
|
||||
item->onClicked = defaultOnClicked;
|
||||
|
||||
[m->items addObject:[NSValue valueWithPointer:item]];
|
||||
|
||||
return item;
|
||||
|
||||
} // @autoreleasepool
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeRegular, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
|
||||
{
|
||||
return newItem(m, typeCheckbox, name);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
|
||||
{
|
||||
// duplicate check is in the register:to: selector
|
||||
return newItem(m, typeQuit, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
|
||||
{
|
||||
// duplicate check is in the register:to: selector
|
||||
return newItem(m, typePreferences, NULL);
|
||||
}
|
||||
|
||||
uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
|
||||
{
|
||||
// duplicate check is in the register:to: selector
|
||||
return newItem(m, typeAbout, NULL);
|
||||
}
|
||||
|
||||
void uiMenuAppendSeparator(uiMenu *m)
|
||||
{
|
||||
newItem(m, typeSeparator, NULL);
|
||||
}
|
||||
|
||||
uiMenu *uiNewMenu(const char *name)
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
uiMenu *m;
|
||||
|
||||
if (menusFinalized)
|
||||
uiprivUserBug("You can't create a new menu after menus have been finalized.");
|
||||
if (menus == nil)
|
||||
menus = [NSMutableArray new];
|
||||
|
||||
m = uiprivNew(uiMenu);
|
||||
|
||||
m->menu = [[NSMenu alloc] initWithTitle:uiprivToNSString(name)];
|
||||
// use automatic menu item enabling for all menus for consistency's sake
|
||||
|
||||
m->item = [[NSMenuItem alloc] initWithTitle:uiprivToNSString(name) action:NULL keyEquivalent:@""];
|
||||
[m->item setSubmenu:m->menu];
|
||||
|
||||
m->items = [NSMutableArray new];
|
||||
|
||||
[[uiprivNSApp() mainMenu] addItem:m->item];
|
||||
|
||||
[menus addObject:[NSValue valueWithPointer:m]];
|
||||
|
||||
return m;
|
||||
|
||||
} // @autoreleasepool
|
||||
}
|
||||
|
||||
void uiprivFinalizeMenus(void)
|
||||
{
|
||||
menusFinalized = YES;
|
||||
}
|
||||
|
||||
void uiprivUninitMenus(void)
|
||||
{
|
||||
if (menus == NULL)
|
||||
return;
|
||||
[menus enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
|
||||
NSValue *v;
|
||||
uiMenu *m;
|
||||
|
||||
v = (NSValue *) obj;
|
||||
m = (uiMenu *) [v pointerValue];
|
||||
[m->items enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
|
||||
NSValue *v;
|
||||
uiMenuItem *mi;
|
||||
|
||||
v = (NSValue *) obj;
|
||||
mi = (uiMenuItem *) [v pointerValue];
|
||||
uiprivFree(mi);
|
||||
}];
|
||||
[m->items release];
|
||||
uiprivFree(m);
|
||||
}];
|
||||
[menus release];
|
||||
}
|
||||
64
dep/libui/darwin/meson.build
Normal file
64
dep/libui/darwin/meson.build
Normal file
@@ -0,0 +1,64 @@
|
||||
# 23 march 2019
|
||||
|
||||
libui_sources += [
|
||||
'darwin/aat.m',
|
||||
'darwin/alloc.m',
|
||||
'darwin/area.m',
|
||||
'darwin/areaevents.m',
|
||||
'darwin/attrstr.m',
|
||||
'darwin/autolayout.m',
|
||||
'darwin/box.m',
|
||||
'darwin/button.m',
|
||||
'darwin/checkbox.m',
|
||||
'darwin/colorbutton.m',
|
||||
'darwin/combobox.m',
|
||||
'darwin/control.m',
|
||||
'darwin/datetimepicker.m',
|
||||
'darwin/debug.m',
|
||||
'darwin/draw.m',
|
||||
'darwin/drawtext.m',
|
||||
'darwin/editablecombo.m',
|
||||
'darwin/entry.m',
|
||||
'darwin/fontbutton.m',
|
||||
'darwin/fontmatch.m',
|
||||
'darwin/fonttraits.m',
|
||||
'darwin/fontvariation.m',
|
||||
'darwin/form.m',
|
||||
'darwin/future.m',
|
||||
'darwin/graphemes.m',
|
||||
'darwin/grid.m',
|
||||
'darwin/group.m',
|
||||
'darwin/image.m',
|
||||
'darwin/label.m',
|
||||
'darwin/main.m',
|
||||
'darwin/map.m',
|
||||
'darwin/menu.m',
|
||||
'darwin/multilineentry.m',
|
||||
'darwin/opentype.m',
|
||||
'darwin/progressbar.m',
|
||||
'darwin/radiobuttons.m',
|
||||
'darwin/scrollview.m',
|
||||
'darwin/separator.m',
|
||||
'darwin/slider.m',
|
||||
'darwin/spinbox.m',
|
||||
'darwin/stddialogs.m',
|
||||
'darwin/tab.m',
|
||||
'darwin/table.m',
|
||||
'darwin/tablecolumn.m',
|
||||
'darwin/text.m',
|
||||
'darwin/undocumented.m',
|
||||
'darwin/util.m',
|
||||
'darwin/window.m',
|
||||
'darwin/winmoveresize.m',
|
||||
]
|
||||
|
||||
libui_deps += [
|
||||
meson.get_compiler('objc').find_library('objc',
|
||||
required: true),
|
||||
dependency('appleframeworks',
|
||||
modules: ['Foundation', 'AppKit'],
|
||||
required: true),
|
||||
]
|
||||
libui_soversion = 'A'
|
||||
# the / is required by some older versions of OS X
|
||||
libui_rpath = '@executable_path/'
|
||||
233
dep/libui/darwin/multilineentry.m
Normal file
233
dep/libui/darwin/multilineentry.m
Normal file
@@ -0,0 +1,233 @@
|
||||
// 8 december 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// NSTextView has no intrinsic content size by default, which wreaks havoc on a pure-Auto Layout system
|
||||
// we'll have to take over to get it to work
|
||||
// see also http://stackoverflow.com/questions/24210153/nstextview-not-properly-resizing-with-auto-layout and http://stackoverflow.com/questions/11237622/using-autolayout-with-expanding-nstextviews
|
||||
@interface intrinsicSizeTextView : NSTextView {
|
||||
uiMultilineEntry *libui_e;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e;
|
||||
@end
|
||||
|
||||
struct uiMultilineEntry {
|
||||
uiDarwinControl c;
|
||||
NSScrollView *sv;
|
||||
intrinsicSizeTextView *tv;
|
||||
uiprivScrollViewData *d;
|
||||
void (*onChanged)(uiMultilineEntry *, void *);
|
||||
void *onChangedData;
|
||||
BOOL changing;
|
||||
};
|
||||
|
||||
@implementation intrinsicSizeTextView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self)
|
||||
self->libui_e = e;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSTextContainer *textContainer;
|
||||
NSLayoutManager *layoutManager;
|
||||
NSRect rect;
|
||||
|
||||
textContainer = [self textContainer];
|
||||
layoutManager = [self layoutManager];
|
||||
[layoutManager ensureLayoutForTextContainer:textContainer];
|
||||
rect = [layoutManager usedRectForTextContainer:textContainer];
|
||||
return rect.size;
|
||||
}
|
||||
|
||||
- (void)didChangeText
|
||||
{
|
||||
[super didChangeText];
|
||||
[self invalidateIntrinsicContentSize];
|
||||
if (!self->libui_e->changing)
|
||||
(*(self->libui_e->onChanged))(self->libui_e, self->libui_e->onChangedData);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiMultilineEntry, sv)
|
||||
|
||||
static void uiMultilineEntryDestroy(uiControl *c)
|
||||
{
|
||||
uiMultilineEntry *e = uiMultilineEntry(c);
|
||||
|
||||
uiprivScrollViewFreeData(e->sv, e->d);
|
||||
[e->tv release];
|
||||
[e->sv release];
|
||||
uiFreeControl(uiControl(e));
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiMultilineEntry *e, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
char *uiMultilineEntryText(uiMultilineEntry *e)
|
||||
{
|
||||
return uiDarwinNSStringToText([e->tv string]);
|
||||
}
|
||||
|
||||
void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)
|
||||
{
|
||||
[[e->tv textStorage] replaceCharactersInRange:NSMakeRange(0, [[e->tv string] length])
|
||||
withString:uiprivToNSString(text)];
|
||||
// must be called explicitly according to the documentation of shouldChangeTextInRange:replacementString:
|
||||
e->changing = YES;
|
||||
[e->tv didChangeText];
|
||||
e->changing = NO;
|
||||
}
|
||||
|
||||
// TODO scroll to end?
|
||||
void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)
|
||||
{
|
||||
[[e->tv textStorage] replaceCharactersInRange:NSMakeRange([[e->tv string] length], 0)
|
||||
withString:uiprivToNSString(text)];
|
||||
e->changing = YES;
|
||||
[e->tv didChangeText];
|
||||
e->changing = NO;
|
||||
}
|
||||
|
||||
void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)
|
||||
{
|
||||
e->onChanged = f;
|
||||
e->onChangedData = data;
|
||||
}
|
||||
|
||||
int uiMultilineEntryReadOnly(uiMultilineEntry *e)
|
||||
{
|
||||
return [e->tv isEditable] == NO;
|
||||
}
|
||||
|
||||
void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)
|
||||
{
|
||||
BOOL editable;
|
||||
|
||||
editable = YES;
|
||||
if (readonly)
|
||||
editable = NO;
|
||||
[e->tv setEditable:editable];
|
||||
}
|
||||
|
||||
static uiMultilineEntry *finishMultilineEntry(BOOL hscroll)
|
||||
{
|
||||
uiMultilineEntry *e;
|
||||
NSFont *font;
|
||||
uiprivScrollViewCreateParams p;
|
||||
|
||||
uiDarwinNewControl(uiMultilineEntry, e);
|
||||
|
||||
e->tv = [[intrinsicSizeTextView alloc] initWithFrame:NSZeroRect e:e];
|
||||
|
||||
// verified against Interface Builder for a sufficiently customized text view
|
||||
|
||||
// NSText properties:
|
||||
// this is what Interface Builder sets the background color to
|
||||
[e->tv setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];
|
||||
[e->tv setDrawsBackground:YES];
|
||||
[e->tv setEditable:YES];
|
||||
[e->tv setSelectable:YES];
|
||||
[e->tv setFieldEditor:NO];
|
||||
[e->tv setRichText:NO];
|
||||
[e->tv setImportsGraphics:NO];
|
||||
[e->tv setUsesFontPanel:NO];
|
||||
[e->tv setRulerVisible:NO];
|
||||
// we'll handle font last
|
||||
// while setAlignment: has been around since 10.0, the named constant "NSTextAlignmentNatural" seems to have only been introduced in 10.11
|
||||
#define ourNSTextAlignmentNatural 4
|
||||
[e->tv setAlignment:ourNSTextAlignmentNatural];
|
||||
// textColor is set to nil, just keep the dfault
|
||||
[e->tv setBaseWritingDirection:NSWritingDirectionNatural];
|
||||
[e->tv setHorizontallyResizable:NO];
|
||||
[e->tv setVerticallyResizable:YES];
|
||||
|
||||
// NSTextView properties:
|
||||
[e->tv setAllowsDocumentBackgroundColorChange:NO];
|
||||
[e->tv setAllowsUndo:YES];
|
||||
// default paragraph style is nil; keep default
|
||||
[e->tv setAllowsImageEditing:NO];
|
||||
[e->tv setAutomaticQuoteSubstitutionEnabled:NO];
|
||||
[e->tv setAutomaticLinkDetectionEnabled:NO];
|
||||
[e->tv setDisplaysLinkToolTips:YES];
|
||||
[e->tv setUsesRuler:NO];
|
||||
[e->tv setUsesInspectorBar:NO];
|
||||
[e->tv setSelectionGranularity:NSSelectByCharacter];
|
||||
// there is a dedicated named insertion point color but oh well
|
||||
[e->tv setInsertionPointColor:[NSColor controlTextColor]];
|
||||
// typing attributes is nil; keep default (we change it below for fonts though)
|
||||
[e->tv setSmartInsertDeleteEnabled:NO];
|
||||
[e->tv setContinuousSpellCheckingEnabled:NO];
|
||||
[e->tv setGrammarCheckingEnabled:NO];
|
||||
[e->tv setUsesFindPanel:YES];
|
||||
[e->tv setEnabledTextCheckingTypes:0];
|
||||
[e->tv setAutomaticDashSubstitutionEnabled:NO];
|
||||
[e->tv setAutomaticDataDetectionEnabled:NO];
|
||||
[e->tv setAutomaticSpellingCorrectionEnabled:NO];
|
||||
[e->tv setAutomaticTextReplacementEnabled:NO];
|
||||
[e->tv setUsesFindBar:NO];
|
||||
[e->tv setIncrementalSearchingEnabled:NO];
|
||||
|
||||
// NSTextContainer properties:
|
||||
[[e->tv textContainer] setWidthTracksTextView:YES];
|
||||
[[e->tv textContainer] setHeightTracksTextView:NO];
|
||||
|
||||
// NSLayoutManager properties:
|
||||
[[e->tv layoutManager] setAllowsNonContiguousLayout:YES];
|
||||
|
||||
// now just to be safe; this will do some of the above but whatever
|
||||
uiprivDisableAutocorrect(e->tv);
|
||||
|
||||
// see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html
|
||||
// notice we don't use the Auto Layout code; see scrollview.m for more details
|
||||
[e->tv setMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
|
||||
[e->tv setVerticallyResizable:YES];
|
||||
[e->tv setHorizontallyResizable:hscroll];
|
||||
if (hscroll) {
|
||||
[e->tv setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
|
||||
[[e->tv textContainer] setWidthTracksTextView:NO];
|
||||
} else {
|
||||
[e->tv setAutoresizingMask:NSViewWidthSizable];
|
||||
[[e->tv textContainer] setWidthTracksTextView:YES];
|
||||
}
|
||||
[[e->tv textContainer] setContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
|
||||
|
||||
// don't use uiDarwinSetControlFont() directly; we have to do a little extra work to set the font
|
||||
font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
|
||||
[e->tv setTypingAttributes:[NSDictionary
|
||||
dictionaryWithObject:font
|
||||
forKey:NSFontAttributeName]];
|
||||
// e->tv font from Interface Builder is nil, but setFont:nil throws an exception
|
||||
// let's just set it to the standard control font anyway, just to be safe
|
||||
[e->tv setFont:font];
|
||||
|
||||
memset(&p, 0, sizeof (uiprivScrollViewCreateParams));
|
||||
p.DocumentView = e->tv;
|
||||
// this is what Interface Builder sets it to
|
||||
p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
|
||||
p.DrawsBackground = YES;
|
||||
p.Bordered = YES;
|
||||
p.HScroll = hscroll;
|
||||
p.VScroll = YES;
|
||||
e->sv = uiprivMkScrollView(&p, &(e->d));
|
||||
|
||||
uiMultilineEntryOnChanged(e, defaultOnChanged, NULL);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
uiMultilineEntry *uiNewMultilineEntry(void)
|
||||
{
|
||||
return finishMultilineEntry(NO);
|
||||
}
|
||||
|
||||
uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)
|
||||
{
|
||||
return finishMultilineEntry(YES);
|
||||
}
|
||||
113
dep/libui/darwin/opentype.m
Normal file
113
dep/libui/darwin/opentype.m
Normal file
@@ -0,0 +1,113 @@
|
||||
// 11 may 2017
|
||||
#import "uipriv_darwin.h"
|
||||
#import "attrstr.h"
|
||||
|
||||
struct addCTFeatureEntryParams {
|
||||
CFMutableArrayRef array;
|
||||
const void *tagKey;
|
||||
BOOL tagIsNumber;
|
||||
CFNumberType tagType;
|
||||
const void *tagValue;
|
||||
const void *valueKey;
|
||||
CFNumberType valueType;
|
||||
const void *valueValue;
|
||||
};
|
||||
|
||||
static void addCTFeatureEntry(struct addCTFeatureEntryParams *p)
|
||||
{
|
||||
CFDictionaryRef featureDict;
|
||||
CFNumberRef tagNum, valueNum;
|
||||
const void *keys[2], *values[2];
|
||||
|
||||
keys[0] = p->tagKey;
|
||||
tagNum = NULL;
|
||||
values[0] = p->tagValue;
|
||||
if (p->tagIsNumber) {
|
||||
tagNum = CFNumberCreate(NULL, p->tagType, p->tagValue);
|
||||
values[0] = tagNum;
|
||||
}
|
||||
|
||||
keys[1] = p->valueKey;
|
||||
valueNum = CFNumberCreate(NULL, p->valueType, p->valueValue);
|
||||
values[1] = valueNum;
|
||||
|
||||
featureDict = CFDictionaryCreate(NULL,
|
||||
keys, values, 2,
|
||||
// TODO are these correct?
|
||||
&kCFCopyStringDictionaryKeyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
if (featureDict == NULL) {
|
||||
// TODO
|
||||
}
|
||||
CFArrayAppendValue(p->array, featureDict);
|
||||
|
||||
CFRelease(featureDict);
|
||||
CFRelease(valueNum);
|
||||
if (p->tagIsNumber)
|
||||
CFRelease(tagNum);
|
||||
}
|
||||
|
||||
static uiForEach otfArrayForEachAAT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
|
||||
{
|
||||
__block struct addCTFeatureEntryParams p;
|
||||
|
||||
p.array = (CFMutableArrayRef) data;
|
||||
p.tagIsNumber = YES;
|
||||
uiprivOpenTypeToAAT(a, b, c, d, value, ^(uint16_t type, uint16_t selector) {
|
||||
p.tagKey = kCTFontFeatureTypeIdentifierKey;
|
||||
p.tagType = kCFNumberSInt16Type;
|
||||
p.tagValue = (const SInt16 *) (&type);
|
||||
p.valueKey = kCTFontFeatureSelectorIdentifierKey;
|
||||
p.valueType = kCFNumberSInt16Type;
|
||||
p.valueValue = (const SInt16 *) (&selector);
|
||||
addCTFeatureEntry(&p);
|
||||
});
|
||||
return uiForEachContinue;
|
||||
}
|
||||
|
||||
// TODO find out which fonts differ in AAT small caps and test them with this
|
||||
static uiForEach otfArrayForEachOT(const uiOpenTypeFeatures *otf, char a, char b, char c, char d, uint32_t value, void *data)
|
||||
{
|
||||
struct addCTFeatureEntryParams p;
|
||||
char tagcstr[5];
|
||||
CFStringRef tagstr;
|
||||
|
||||
p.array = (CFMutableArrayRef) data;
|
||||
|
||||
p.tagKey = *uiprivFUTURE_kCTFontOpenTypeFeatureTag;
|
||||
p.tagIsNumber = NO;
|
||||
tagcstr[0] = a;
|
||||
tagcstr[1] = b;
|
||||
tagcstr[2] = c;
|
||||
tagcstr[3] = d;
|
||||
tagcstr[4] = '\0';
|
||||
tagstr = CFStringCreateWithCString(NULL, tagcstr, kCFStringEncodingUTF8);
|
||||
if (tagstr == NULL) {
|
||||
// TODO
|
||||
}
|
||||
p.tagValue = tagstr;
|
||||
|
||||
p.valueKey = *uiprivFUTURE_kCTFontOpenTypeFeatureValue;
|
||||
p.valueType = kCFNumberSInt32Type;
|
||||
p.valueValue = (const SInt32 *) (&value);
|
||||
addCTFeatureEntry(&p);
|
||||
|
||||
CFRelease(tagstr);
|
||||
return uiForEachContinue;
|
||||
}
|
||||
|
||||
CFArrayRef uiprivOpenTypeFeaturesToCTFeatures(const uiOpenTypeFeatures *otf)
|
||||
{
|
||||
CFMutableArrayRef array;
|
||||
uiOpenTypeFeaturesForEachFunc f;
|
||||
|
||||
array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
|
||||
if (array == NULL) {
|
||||
// TODO
|
||||
}
|
||||
f = otfArrayForEachAAT;
|
||||
if (uiprivFUTURE_kCTFontOpenTypeFeatureTag != NULL && uiprivFUTURE_kCTFontOpenTypeFeatureValue != NULL)
|
||||
f = otfArrayForEachOT;
|
||||
uiOpenTypeFeaturesForEach(otf, f, array);
|
||||
return array;
|
||||
}
|
||||
78
dep/libui/darwin/progressbar.m
Normal file
78
dep/libui/darwin/progressbar.m
Normal file
@@ -0,0 +1,78 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// NSProgressIndicator has no intrinsic width by default; use the default width in Interface Builder
|
||||
#define progressIndicatorWidth 100
|
||||
|
||||
@interface intrinsicWidthNSProgressIndicator : NSProgressIndicator
|
||||
@end
|
||||
|
||||
@implementation intrinsicWidthNSProgressIndicator
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = progressIndicatorWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiProgressBar {
|
||||
uiDarwinControl c;
|
||||
NSProgressIndicator *pi;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiProgressBar, pi)
|
||||
|
||||
int uiProgressBarValue(uiProgressBar *p)
|
||||
{
|
||||
if ([p->pi isIndeterminate])
|
||||
return -1;
|
||||
return [p->pi doubleValue];
|
||||
}
|
||||
|
||||
void uiProgressBarSetValue(uiProgressBar *p, int value)
|
||||
{
|
||||
if (value == -1) {
|
||||
[p->pi setIndeterminate:YES];
|
||||
[p->pi startAnimation:p->pi];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([p->pi isIndeterminate]) {
|
||||
[p->pi setIndeterminate:NO];
|
||||
[p->pi stopAnimation:p->pi];
|
||||
}
|
||||
|
||||
if (value < 0 || value > 100)
|
||||
uiprivUserBug("Value %d out of range for a uiProgressBar.", value);
|
||||
|
||||
// on 10.8 there's an animation when the progress bar increases, just like with Aero
|
||||
if (value == 100) {
|
||||
[p->pi setMaxValue:101];
|
||||
[p->pi setDoubleValue:101];
|
||||
[p->pi setDoubleValue:100];
|
||||
[p->pi setMaxValue:100];
|
||||
return;
|
||||
}
|
||||
[p->pi setDoubleValue:((double) (value + 1))];
|
||||
[p->pi setDoubleValue:((double) value)];
|
||||
}
|
||||
|
||||
uiProgressBar *uiNewProgressBar(void)
|
||||
{
|
||||
uiProgressBar *p;
|
||||
|
||||
uiDarwinNewControl(uiProgressBar, p);
|
||||
|
||||
p->pi = [[intrinsicWidthNSProgressIndicator alloc] initWithFrame:NSZeroRect];
|
||||
[p->pi setControlSize:NSRegularControlSize];
|
||||
[p->pi setBezeled:YES];
|
||||
[p->pi setStyle:NSProgressIndicatorBarStyle];
|
||||
[p->pi setIndeterminate:NO];
|
||||
|
||||
return p;
|
||||
}
|
||||
207
dep/libui/darwin/radiobuttons.m
Normal file
207
dep/libui/darwin/radiobuttons.m
Normal file
@@ -0,0 +1,207 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO resizing the controlgallery vertically causes the third button to still resize :|
|
||||
|
||||
// In the old days you would use a NSMatrix for this; as of OS X 10.8 this was deprecated and now you need just a bunch of NSButtons with the same superview AND same action method.
|
||||
// This is documented on the NSMatrix page, but the rest of the OS X documentation says to still use NSMatrix.
|
||||
// NSMatrix has weird quirks anyway...
|
||||
|
||||
// LONGTERM 6 units of spacing between buttons, as suggested by Interface Builder?
|
||||
|
||||
@interface radioButtonsDelegate : NSObject {
|
||||
uiRadioButtons *libui_r;
|
||||
}
|
||||
- (id)initWithR:(uiRadioButtons *)r;
|
||||
- (IBAction)onClicked:(id)sender;
|
||||
@end
|
||||
|
||||
struct uiRadioButtons {
|
||||
uiDarwinControl c;
|
||||
NSView *view;
|
||||
NSMutableArray *buttons;
|
||||
NSMutableArray *constraints;
|
||||
NSLayoutConstraint *lastv;
|
||||
radioButtonsDelegate *delegate;
|
||||
void (*onSelected)(uiRadioButtons *, void *);
|
||||
void *onSelectedData;
|
||||
};
|
||||
|
||||
@implementation radioButtonsDelegate
|
||||
|
||||
- (id)initWithR:(uiRadioButtons *)r
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->libui_r = r;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (IBAction)onClicked:(id)sender
|
||||
{
|
||||
uiRadioButtons *r = self->libui_r;
|
||||
|
||||
(*(r->onSelected))(r, r->onSelectedData);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiRadioButtons, view)
|
||||
|
||||
static void defaultOnSelected(uiRadioButtons *r, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
static void uiRadioButtonsDestroy(uiControl *c)
|
||||
{
|
||||
uiRadioButtons *r = uiRadioButtons(c);
|
||||
NSButton *b;
|
||||
|
||||
// drop the constraints
|
||||
[r->view removeConstraints:r->constraints];
|
||||
[r->constraints release];
|
||||
if (r->lastv != nil)
|
||||
[r->lastv release];
|
||||
// destroy the buttons
|
||||
for (b in r->buttons) {
|
||||
[b setTarget:nil];
|
||||
[b removeFromSuperview];
|
||||
}
|
||||
[r->buttons release];
|
||||
// destroy the delegate
|
||||
[r->delegate release];
|
||||
// and destroy ourselves
|
||||
[r->view release];
|
||||
uiFreeControl(uiControl(r));
|
||||
}
|
||||
|
||||
static NSButton *buttonAt(uiRadioButtons *r, int n)
|
||||
{
|
||||
return (NSButton *) [r->buttons objectAtIndex:n];
|
||||
}
|
||||
|
||||
void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
|
||||
{
|
||||
NSButton *b, *b2;
|
||||
NSLayoutConstraint *constraint;
|
||||
|
||||
b = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[b setTitle:uiprivToNSString(text)];
|
||||
[b setButtonType:NSRadioButton];
|
||||
// doesn't seem to have an associated bezel style
|
||||
[b setBordered:NO];
|
||||
[b setTransparent:NO];
|
||||
uiDarwinSetControlFont(b, NSRegularControlSize);
|
||||
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
[b setTarget:r->delegate];
|
||||
[b setAction:@selector(onClicked:)];
|
||||
|
||||
[r->buttons addObject:b];
|
||||
[r->view addSubview:b];
|
||||
|
||||
// pin horizontally to the edges of the superview
|
||||
constraint = uiprivMkConstraint(b, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiRadioButtons button leading constraint");
|
||||
[r->view addConstraint:constraint];
|
||||
[r->constraints addObject:constraint];
|
||||
constraint = uiprivMkConstraint(b, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiRadioButtons button trailing constraint");
|
||||
[r->view addConstraint:constraint];
|
||||
[r->constraints addObject:constraint];
|
||||
|
||||
// if this is the first view, pin it to the top
|
||||
// otherwise pin to the bottom of the last
|
||||
if ([r->buttons count] == 1)
|
||||
constraint = uiprivMkConstraint(b, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiRadioButtons first button top constraint");
|
||||
else {
|
||||
b2 = buttonAt(r, [r->buttons count] - 2);
|
||||
constraint = uiprivMkConstraint(b, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
b2, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiRadioButtons non-first button top constraint");
|
||||
}
|
||||
[r->view addConstraint:constraint];
|
||||
[r->constraints addObject:constraint];
|
||||
|
||||
// if there is a previous bottom constraint, remove it
|
||||
if (r->lastv != nil) {
|
||||
[r->view removeConstraint:r->lastv];
|
||||
[r->constraints removeObject:r->lastv];
|
||||
[r->lastv release];
|
||||
}
|
||||
|
||||
// and make the new bottom constraint
|
||||
r->lastv = uiprivMkConstraint(b, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
r->view, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiRadioButtons last button bottom constraint");
|
||||
[r->view addConstraint:r->lastv];
|
||||
[r->constraints addObject:r->lastv];
|
||||
[r->lastv retain];
|
||||
}
|
||||
|
||||
int uiRadioButtonsSelected(uiRadioButtons *r)
|
||||
{
|
||||
NSButton *b;
|
||||
NSUInteger i;
|
||||
|
||||
for (i = 0; i < [r->buttons count]; i++) {
|
||||
b = (NSButton *) [r->buttons objectAtIndex:i];
|
||||
if ([b state] == NSOnState)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void uiRadioButtonsSetSelected(uiRadioButtons *r, int n)
|
||||
{
|
||||
NSButton *b;
|
||||
NSInteger state;
|
||||
|
||||
state = NSOnState;
|
||||
if (n == -1) {
|
||||
n = uiRadioButtonsSelected(r);
|
||||
if (n == -1) // from nothing to nothing; do nothing
|
||||
return;
|
||||
state = NSOffState;
|
||||
}
|
||||
b = (NSButton *) [r->buttons objectAtIndex:n];
|
||||
[b setState:state];
|
||||
}
|
||||
|
||||
void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)
|
||||
{
|
||||
r->onSelected = f;
|
||||
r->onSelectedData = data;
|
||||
}
|
||||
|
||||
uiRadioButtons *uiNewRadioButtons(void)
|
||||
{
|
||||
uiRadioButtons *r;
|
||||
|
||||
uiDarwinNewControl(uiRadioButtons, r);
|
||||
|
||||
r->view = [[NSView alloc] initWithFrame:NSZeroRect];
|
||||
r->buttons = [NSMutableArray new];
|
||||
r->constraints = [NSMutableArray new];
|
||||
|
||||
r->delegate = [[radioButtonsDelegate alloc] initWithR:r];
|
||||
|
||||
uiRadioButtonsOnSelected(r, defaultOnSelected, NULL);
|
||||
|
||||
return r;
|
||||
}
|
||||
61
dep/libui/darwin/scrollview.m
Normal file
61
dep/libui/darwin/scrollview.m
Normal file
@@ -0,0 +1,61 @@
|
||||
// 27 may 2016
|
||||
#include "uipriv_darwin.h"
|
||||
|
||||
// see http://stackoverflow.com/questions/37979445/how-do-i-properly-set-up-a-scrolling-nstableview-using-auto-layout-what-ive-tr for why we don't use auto layout
|
||||
// TODO do the same with uiGroup and uiTab?
|
||||
|
||||
struct uiprivScrollViewData {
|
||||
BOOL hscroll;
|
||||
BOOL vscroll;
|
||||
};
|
||||
|
||||
NSScrollView *uiprivMkScrollView(uiprivScrollViewCreateParams *p, uiprivScrollViewData **dout)
|
||||
{
|
||||
NSScrollView *sv;
|
||||
NSBorderType border;
|
||||
uiprivScrollViewData *d;
|
||||
|
||||
sv = [[NSScrollView alloc] initWithFrame:NSZeroRect];
|
||||
if (p->BackgroundColor != nil)
|
||||
[sv setBackgroundColor:p->BackgroundColor];
|
||||
[sv setDrawsBackground:p->DrawsBackground];
|
||||
border = NSNoBorder;
|
||||
if (p->Bordered)
|
||||
border = NSBezelBorder;
|
||||
// document view seems to set the cursor properly
|
||||
[sv setBorderType:border];
|
||||
[sv setAutohidesScrollers:YES];
|
||||
[sv setHasHorizontalRuler:NO];
|
||||
[sv setHasVerticalRuler:NO];
|
||||
[sv setRulersVisible:NO];
|
||||
[sv setScrollerKnobStyle:NSScrollerKnobStyleDefault];
|
||||
// the scroller style is documented as being set by default for us
|
||||
// LONGTERM verify line and page for programmatically created NSTableView
|
||||
[sv setScrollsDynamically:YES];
|
||||
[sv setFindBarPosition:NSScrollViewFindBarPositionAboveContent];
|
||||
[sv setUsesPredominantAxisScrolling:NO];
|
||||
[sv setHorizontalScrollElasticity:NSScrollElasticityAutomatic];
|
||||
[sv setVerticalScrollElasticity:NSScrollElasticityAutomatic];
|
||||
[sv setAllowsMagnification:NO];
|
||||
|
||||
[sv setDocumentView:p->DocumentView];
|
||||
d = uiprivNew(uiprivScrollViewData);
|
||||
uiprivScrollViewSetScrolling(sv, d, p->HScroll, p->VScroll);
|
||||
|
||||
*dout = d;
|
||||
return sv;
|
||||
}
|
||||
|
||||
// based on http://blog.bjhomer.com/2014/08/nsscrollview-and-autolayout.html because (as pointed out there) Apple's official guide is really only for iOS
|
||||
void uiprivScrollViewSetScrolling(NSScrollView *sv, uiprivScrollViewData *d, BOOL hscroll, BOOL vscroll)
|
||||
{
|
||||
d->hscroll = hscroll;
|
||||
[sv setHasHorizontalScroller:d->hscroll];
|
||||
d->vscroll = vscroll;
|
||||
[sv setHasVerticalScroller:d->vscroll];
|
||||
}
|
||||
|
||||
void uiprivScrollViewFreeData(NSScrollView *sv, uiprivScrollViewData *d)
|
||||
{
|
||||
uiprivFree(d);
|
||||
}
|
||||
45
dep/libui/darwin/separator.m
Normal file
45
dep/libui/darwin/separator.m
Normal file
@@ -0,0 +1,45 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO make this intrinsic
|
||||
#define separatorWidth 96
|
||||
#define separatorHeight 96
|
||||
|
||||
struct uiSeparator {
|
||||
uiDarwinControl c;
|
||||
NSBox *box;
|
||||
};
|
||||
|
||||
uiDarwinControlAllDefaults(uiSeparator, box)
|
||||
|
||||
uiSeparator *uiNewHorizontalSeparator(void)
|
||||
{
|
||||
uiSeparator *s;
|
||||
|
||||
uiDarwinNewControl(uiSeparator, s);
|
||||
|
||||
// make the initial width >= initial height to force horizontal
|
||||
s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 100, 1)];
|
||||
[s->box setBoxType:NSBoxSeparator];
|
||||
[s->box setBorderType:NSGrooveBorder];
|
||||
[s->box setTransparent:NO];
|
||||
[s->box setTitlePosition:NSNoTitle];
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
uiSeparator *uiNewVerticalSeparator(void)
|
||||
{
|
||||
uiSeparator *s;
|
||||
|
||||
uiDarwinNewControl(uiSeparator, s);
|
||||
|
||||
// make the initial height >= initial width to force vertical
|
||||
s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 1, 100)];
|
||||
[s->box setBoxType:NSBoxSeparator];
|
||||
[s->box setBorderType:NSGrooveBorder];
|
||||
[s->box setTransparent:NO];
|
||||
[s->box setTitlePosition:NSNoTitle];
|
||||
|
||||
return s;
|
||||
}
|
||||
79
dep/libui/darwin/sierra.h
Normal file
79
dep/libui/darwin/sierra.h
Normal file
@@ -0,0 +1,79 @@
|
||||
// 2 june 2017
|
||||
|
||||
// The OS X 10.12 SDK introduces a number of new names for
|
||||
// existing constants to align the naming conventions of
|
||||
// Objective-C and Swift (particularly in AppKit).
|
||||
//
|
||||
// Unfortunately, in a baffling move, instead of using the existing
|
||||
// AvailabilityMacros.h method of marking things deprecated, they
|
||||
// rewrote the relevant constants in ways that make
|
||||
// source-compatible building much more annoying:
|
||||
//
|
||||
// - The replacement names are now the only names in the enum
|
||||
// or define sets they used to be in.
|
||||
// - The old names are provided as static const variables, which
|
||||
// means any code that used the old names in a switch case now
|
||||
// spit out a compiler warning in strict C99 mode (TODO and in C++ mode?).
|
||||
// - The old names are marked with new deprecated-symbol
|
||||
// macros that are *not* compatible with the AvailabilityMacros.h
|
||||
// macros, meaning their deprecation warnings still come
|
||||
// through. (It should be noted that AvailabilityMacros.h was still
|
||||
// updated for 10.12 regardless, hence our #ifdef below.)
|
||||
//
|
||||
// As far as I can gather, these facts are not documented *at all*, so
|
||||
// in the meantime, other open-source projects just use their own
|
||||
// #defines to maintain source compatibility, either by making the
|
||||
// new names available everywhere or the old ones un-deprecated.
|
||||
// We choose the latter.
|
||||
// TODO file a radar on the issue (after determining C++ compatibility) so this can be pinned down once and for all
|
||||
// TODO after that, link my stackoverflow question here too
|
||||
// TODO make sure this #ifdef does actually work on older systems
|
||||
|
||||
#ifdef MAC_OS_X_VERSION_10_12
|
||||
|
||||
#define NSControlKeyMask NSEventModifierFlagControl
|
||||
#define NSAlternateKeyMask NSEventModifierFlagOption
|
||||
#define NSShiftKeyMask NSEventModifierFlagShift
|
||||
#define NSCommandKeyMask NSEventModifierFlagCommand
|
||||
|
||||
#define NSLeftMouseDown NSEventTypeLeftMouseDown
|
||||
#define NSRightMouseDown NSEventTypeRightMouseDown
|
||||
#define NSOtherMouseDown NSEventTypeOtherMouseDown
|
||||
#define NSLeftMouseUp NSEventTypeLeftMouseUp
|
||||
#define NSRightMouseUp NSEventTypeRightMouseUp
|
||||
#define NSOtherMouseUp NSEventTypeOtherMouseUp
|
||||
#define NSLeftMouseDragged NSEventTypeLeftMouseDragged
|
||||
#define NSRightMouseDragged NSEventTypeRightMouseDragged
|
||||
#define NSOtherMouseDragged NSEventTypeOtherMouseDragged
|
||||
#define NSKeyDown NSEventTypeKeyDown
|
||||
#define NSKeyUp NSEventTypeKeyUp
|
||||
#define NSFlagsChanged NSEventTypeFlagsChanged
|
||||
#define NSApplicationDefined NSEventTypeApplicationDefined
|
||||
#define NSPeriodic NSEventTypePeriodic
|
||||
#define NSMouseMoved NSEventTypeMouseMoved
|
||||
|
||||
#define NSRegularControlSize NSControlSizeRegular
|
||||
#define NSSmallControlSize NSControlSizeSmall
|
||||
|
||||
#define NSAnyEventMask NSEventMaskAny
|
||||
#define NSLeftMouseDraggedMask NSEventMaskLeftMouseDragged
|
||||
#define NSLeftMouseUpMask NSEventMaskLeftMouseUp
|
||||
|
||||
#define NSTickMarkAbove NSTickMarkPositionAbove
|
||||
|
||||
#define NSLinearSlider NSSliderTypeLinear
|
||||
|
||||
#define NSInformationalAlertStyle NSAlertStyleInformational
|
||||
#define NSCriticalAlertStyle NSAlertStyleCritical
|
||||
|
||||
#define NSBorderlessWindowMask NSWindowStyleMaskBorderless
|
||||
#define NSTitledWindowMask NSWindowStyleMaskTitled
|
||||
#define NSClosableWindowMask NSWindowStyleMaskClosable
|
||||
#define NSMiniaturizableWindowMask NSWindowStyleMaskMiniaturizable
|
||||
#define NSResizableWindowMask NSWindowStyleMaskResizable
|
||||
|
||||
#endif
|
||||
|
||||
// TODO /Users/pietro/src/github.com/andlabs/libui/darwin/stddialogs.m:83:15: warning: 'beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:' is deprecated: first deprecated in macOS 10.10 - Use -beginSheetModalForWindow:completionHandler: instead [-Wdeprecated-declarations]
|
||||
|
||||
// TODO https://developer.apple.com/library/content/releasenotes/Miscellaneous/RN-Foundation-OSX10.12/
|
||||
147
dep/libui/darwin/slider.m
Normal file
147
dep/libui/darwin/slider.m
Normal file
@@ -0,0 +1,147 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// Horizontal sliders have no intrinsic width; we'll use the default Interface Builder width for them.
|
||||
// This will also be used for the initial frame size, to ensure the slider is always horizontal (see below).
|
||||
#define sliderWidth 92
|
||||
|
||||
@interface libui_intrinsicWidthNSSlider : NSSlider
|
||||
@end
|
||||
|
||||
@implementation libui_intrinsicWidthNSSlider
|
||||
|
||||
- (NSSize)intrinsicContentSize
|
||||
{
|
||||
NSSize s;
|
||||
|
||||
s = [super intrinsicContentSize];
|
||||
s.width = sliderWidth;
|
||||
return s;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct uiSlider {
|
||||
uiDarwinControl c;
|
||||
NSSlider *slider;
|
||||
void (*onChanged)(uiSlider *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
@interface sliderDelegateClass : NSObject {
|
||||
uiprivMap *sliders;
|
||||
}
|
||||
- (IBAction)onChanged:(id)sender;
|
||||
- (void)registerSlider:(uiSlider *)b;
|
||||
- (void)unregisterSlider:(uiSlider *)b;
|
||||
@end
|
||||
|
||||
@implementation sliderDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->sliders = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->sliders);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (IBAction)onChanged:(id)sender
|
||||
{
|
||||
uiSlider *s;
|
||||
|
||||
s = (uiSlider *) uiprivMapGet(self->sliders, sender);
|
||||
(*(s->onChanged))(s, s->onChangedData);
|
||||
}
|
||||
|
||||
- (void)registerSlider:(uiSlider *)s
|
||||
{
|
||||
uiprivMapSet(self->sliders, s->slider, s);
|
||||
[s->slider setTarget:self];
|
||||
[s->slider setAction:@selector(onChanged:)];
|
||||
}
|
||||
|
||||
- (void)unregisterSlider:(uiSlider *)s
|
||||
{
|
||||
[s->slider setTarget:nil];
|
||||
uiprivMapDelete(self->sliders, s->slider);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static sliderDelegateClass *sliderDelegate = nil;
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiSlider, slider)
|
||||
|
||||
static void uiSliderDestroy(uiControl *c)
|
||||
{
|
||||
uiSlider *s = uiSlider(c);
|
||||
|
||||
[sliderDelegate unregisterSlider:s];
|
||||
[s->slider release];
|
||||
uiFreeControl(uiControl(s));
|
||||
}
|
||||
|
||||
int uiSliderValue(uiSlider *s)
|
||||
{
|
||||
return [s->slider integerValue];
|
||||
}
|
||||
|
||||
void uiSliderSetValue(uiSlider *s, int value)
|
||||
{
|
||||
[s->slider setIntegerValue:value];
|
||||
}
|
||||
|
||||
void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
|
||||
{
|
||||
s->onChanged = f;
|
||||
s->onChangedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiSlider *s, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiSlider *uiNewSlider(int min, int max)
|
||||
{
|
||||
uiSlider *s;
|
||||
NSSliderCell *cell;
|
||||
int temp;
|
||||
|
||||
if (min >= max) {
|
||||
temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
uiDarwinNewControl(uiSlider, s);
|
||||
|
||||
// a horizontal slider is defined as one where the width > height, not by a flag
|
||||
// to be safe, don't use NSZeroRect, but make it horizontal from the get-go
|
||||
s->slider = [[libui_intrinsicWidthNSSlider alloc]
|
||||
initWithFrame:NSMakeRect(0, 0, sliderWidth, 2)];
|
||||
[s->slider setMinValue:min];
|
||||
[s->slider setMaxValue:max];
|
||||
[s->slider setAllowsTickMarkValuesOnly:NO];
|
||||
[s->slider setNumberOfTickMarks:0];
|
||||
[s->slider setTickMarkPosition:NSTickMarkAbove];
|
||||
|
||||
cell = (NSSliderCell *) [s->slider cell];
|
||||
[cell setSliderType:NSLinearSlider];
|
||||
|
||||
if (sliderDelegate == nil) {
|
||||
sliderDelegate = [[sliderDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:sliderDelegate];
|
||||
}
|
||||
[sliderDelegate registerSlider:s];
|
||||
uiSliderOnChanged(s, defaultOnChanged, NULL);
|
||||
|
||||
return s;
|
||||
}
|
||||
214
dep/libui/darwin/spinbox.m
Normal file
214
dep/libui/darwin/spinbox.m
Normal file
@@ -0,0 +1,214 @@
|
||||
// 14 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
@interface libui_spinbox : NSView<NSTextFieldDelegate> {
|
||||
NSTextField *tf;
|
||||
NSNumberFormatter *formatter;
|
||||
NSStepper *stepper;
|
||||
|
||||
NSInteger value;
|
||||
NSInteger minimum;
|
||||
NSInteger maximum;
|
||||
|
||||
uiSpinbox *spinbox;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb;
|
||||
// see https://github.com/andlabs/ui/issues/82
|
||||
- (NSInteger)libui_value;
|
||||
- (void)libui_setValue:(NSInteger)val;
|
||||
- (void)setMinimum:(NSInteger)min;
|
||||
- (void)setMaximum:(NSInteger)max;
|
||||
- (IBAction)stepperClicked:(id)sender;
|
||||
- (void)controlTextDidChange:(NSNotification *)note;
|
||||
@end
|
||||
|
||||
struct uiSpinbox {
|
||||
uiDarwinControl c;
|
||||
libui_spinbox *spinbox;
|
||||
void (*onChanged)(uiSpinbox *, void *);
|
||||
void *onChangedData;
|
||||
};
|
||||
|
||||
// yes folks, this varies by operating system! woo!
|
||||
// 10.10 started drawing the NSStepper one point too low, so we have to fix it up conditionally
|
||||
// TODO test this; we'll probably have to substitute 10_9
|
||||
static CGFloat stepperYDelta(void)
|
||||
{
|
||||
// via https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/
|
||||
if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9)
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@implementation libui_spinbox
|
||||
|
||||
- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->tf = uiprivNewEditableTextField();
|
||||
[self->tf setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
self->formatter = [NSNumberFormatter new];
|
||||
[self->formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
|
||||
[self->formatter setLocalizesFormat:NO];
|
||||
[self->formatter setUsesGroupingSeparator:NO];
|
||||
[self->formatter setHasThousandSeparators:NO];
|
||||
[self->formatter setAllowsFloats:NO];
|
||||
[self->tf setFormatter:self->formatter];
|
||||
|
||||
self->stepper = [[NSStepper alloc] initWithFrame:NSZeroRect];
|
||||
[self->stepper setIncrement:1];
|
||||
[self->stepper setValueWraps:NO];
|
||||
[self->stepper setAutorepeat:YES]; // hold mouse button to step repeatedly
|
||||
[self->stepper setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
|
||||
[self->tf setDelegate:self];
|
||||
[self->stepper setTarget:self];
|
||||
[self->stepper setAction:@selector(stepperClicked:)];
|
||||
|
||||
[self addSubview:self->tf];
|
||||
[self addSubview:self->stepper];
|
||||
|
||||
[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeLeading,
|
||||
1, 0,
|
||||
@"uiSpinbox left edge")];
|
||||
[self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTrailing,
|
||||
1, 0,
|
||||
@"uiSpinbox right edge")];
|
||||
[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiSpinbox top edge text field")];
|
||||
[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiSpinbox bottom edge text field")];
|
||||
[self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeTop,
|
||||
1, stepperYDelta(),
|
||||
@"uiSpinbox top edge stepper")];
|
||||
[self addConstraint:uiprivMkConstraint(self->stepper, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self, NSLayoutAttributeBottom,
|
||||
1, stepperYDelta(),
|
||||
@"uiSpinbox bottom edge stepper")];
|
||||
[self addConstraint:uiprivMkConstraint(self->tf, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->stepper, NSLayoutAttributeLeading,
|
||||
1, -3, // arbitrary amount; good enough visually (and it seems to match NSDatePicker too, at least on 10.11, which is even better)
|
||||
@"uiSpinbox space between text field and stepper")];
|
||||
|
||||
self->spinbox = sb;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self->tf setDelegate:nil];
|
||||
[self->tf removeFromSuperview];
|
||||
[self->tf release];
|
||||
[self->formatter release];
|
||||
[self->stepper setTarget:nil];
|
||||
[self->stepper removeFromSuperview];
|
||||
[self->stepper release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSInteger)libui_value
|
||||
{
|
||||
return self->value;
|
||||
}
|
||||
|
||||
- (void)libui_setValue:(NSInteger)val
|
||||
{
|
||||
self->value = val;
|
||||
if (self->value < self->minimum)
|
||||
self->value = self->minimum;
|
||||
if (self->value > self->maximum)
|
||||
self->value = self->maximum;
|
||||
[self->tf setIntegerValue:self->value];
|
||||
[self->stepper setIntegerValue:self->value];
|
||||
}
|
||||
|
||||
- (void)setMinimum:(NSInteger)min
|
||||
{
|
||||
self->minimum = min;
|
||||
[self->formatter setMinimum:[NSNumber numberWithInteger:self->minimum]];
|
||||
[self->stepper setMinValue:((double) (self->minimum))];
|
||||
}
|
||||
|
||||
- (void)setMaximum:(NSInteger)max
|
||||
{
|
||||
self->maximum = max;
|
||||
[self->formatter setMaximum:[NSNumber numberWithInteger:self->maximum]];
|
||||
[self->stepper setMaxValue:((double) (self->maximum))];
|
||||
}
|
||||
|
||||
- (IBAction)stepperClicked:(id)sender
|
||||
{
|
||||
[self libui_setValue:[self->stepper integerValue]];
|
||||
(*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);
|
||||
}
|
||||
|
||||
- (void)controlTextDidChange:(NSNotification *)note
|
||||
{
|
||||
[self libui_setValue:[self->tf integerValue]];
|
||||
(*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiDarwinControlAllDefaults(uiSpinbox, spinbox)
|
||||
|
||||
int uiSpinboxValue(uiSpinbox *s)
|
||||
{
|
||||
return [s->spinbox libui_value];
|
||||
}
|
||||
|
||||
void uiSpinboxSetValue(uiSpinbox *s, int value)
|
||||
{
|
||||
[s->spinbox libui_setValue:value];
|
||||
}
|
||||
|
||||
void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)
|
||||
{
|
||||
s->onChanged = f;
|
||||
s->onChangedData = data;
|
||||
}
|
||||
|
||||
static void defaultOnChanged(uiSpinbox *s, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiSpinbox *uiNewSpinbox(int min, int max)
|
||||
{
|
||||
uiSpinbox *s;
|
||||
int temp;
|
||||
|
||||
if (min >= max) {
|
||||
temp = min;
|
||||
min = max;
|
||||
max = temp;
|
||||
}
|
||||
|
||||
uiDarwinNewControl(uiSpinbox, s);
|
||||
|
||||
s->spinbox = [[libui_spinbox alloc] initWithFrame:NSZeroRect spinbox:s];
|
||||
[s->spinbox setMinimum:min];
|
||||
[s->spinbox setMaximum:max];
|
||||
[s->spinbox libui_setValue:min];
|
||||
|
||||
uiSpinboxOnChanged(s, defaultOnChanged, NULL);
|
||||
|
||||
return s;
|
||||
}
|
||||
123
dep/libui/darwin/stddialogs.m
Normal file
123
dep/libui/darwin/stddialogs.m
Normal file
@@ -0,0 +1,123 @@
|
||||
// 26 june 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// LONGTERM restructure this whole file
|
||||
// LONGTERM explicitly document this works as we want
|
||||
// LONGTERM note that font and color buttons also do this
|
||||
|
||||
#define windowWindow(w) ((NSWindow *) uiControlHandle(uiControl(w)))
|
||||
|
||||
// source of code modal logic: http://stackoverflow.com/questions/604768/wait-for-nsalert-beginsheetmodalforwindow
|
||||
|
||||
// note: whether extensions are actually shown depends on a user setting in Finder; we can't control it here
|
||||
static void setupSavePanel(NSSavePanel *s)
|
||||
{
|
||||
[s setCanCreateDirectories:YES];
|
||||
[s setShowsHiddenFiles:YES];
|
||||
[s setExtensionHidden:NO];
|
||||
[s setCanSelectHiddenExtension:NO];
|
||||
[s setTreatsFilePackagesAsDirectories:YES];
|
||||
}
|
||||
|
||||
static char *runSavePanel(NSWindow *parent, NSSavePanel *s)
|
||||
{
|
||||
char *filename;
|
||||
|
||||
[s beginSheetModalForWindow:parent completionHandler:^(NSInteger result) {
|
||||
[uiprivNSApp() stopModalWithCode:result];
|
||||
}];
|
||||
if ([uiprivNSApp() runModalForWindow:s] != NSFileHandlingPanelOKButton)
|
||||
return NULL;
|
||||
filename = uiDarwinNSStringToText([[s URL] path]);
|
||||
return filename;
|
||||
}
|
||||
|
||||
char *uiOpenFile(uiWindow *parent)
|
||||
{
|
||||
NSOpenPanel *o;
|
||||
|
||||
o = [NSOpenPanel openPanel];
|
||||
[o setCanChooseFiles:YES];
|
||||
[o setCanChooseDirectories:NO];
|
||||
[o setResolvesAliases:NO];
|
||||
[o setAllowsMultipleSelection:NO];
|
||||
setupSavePanel(o);
|
||||
// panel is autoreleased
|
||||
return runSavePanel(windowWindow(parent), o);
|
||||
}
|
||||
|
||||
char *uiSaveFile(uiWindow *parent)
|
||||
{
|
||||
NSSavePanel *s;
|
||||
|
||||
s = [NSSavePanel savePanel];
|
||||
setupSavePanel(s);
|
||||
// panel is autoreleased
|
||||
return runSavePanel(windowWindow(parent), s);
|
||||
}
|
||||
|
||||
// I would use a completion handler for NSAlert as well, but alas NSAlert's are 10.9 and higher only
|
||||
@interface libuiCodeModalAlertPanel : NSObject {
|
||||
NSAlert *panel;
|
||||
NSWindow *parent;
|
||||
}
|
||||
- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w;
|
||||
- (NSInteger)run;
|
||||
- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data;
|
||||
@end
|
||||
|
||||
@implementation libuiCodeModalAlertPanel
|
||||
|
||||
- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w
|
||||
{
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->panel = p;
|
||||
self->parent = w;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)run
|
||||
{
|
||||
[self->panel beginSheetModalForWindow:self->parent
|
||||
modalDelegate:self
|
||||
didEndSelector:@selector(panelEnded:result:data:)
|
||||
contextInfo:NULL];
|
||||
return [uiprivNSApp() runModalForWindow:[self->panel window]];
|
||||
}
|
||||
|
||||
- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data
|
||||
{
|
||||
[uiprivNSApp() stopModalWithCode:result];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void msgbox(NSWindow *parent, const char *title, const char *description, NSAlertStyle style)
|
||||
{
|
||||
NSAlert *a;
|
||||
libuiCodeModalAlertPanel *cm;
|
||||
|
||||
a = [NSAlert new];
|
||||
[a setAlertStyle:style];
|
||||
[a setShowsHelp:NO];
|
||||
[a setShowsSuppressionButton:NO];
|
||||
[a setMessageText:uiprivToNSString(title)];
|
||||
[a setInformativeText:uiprivToNSString(description)];
|
||||
[a addButtonWithTitle:@"OK"];
|
||||
cm = [[libuiCodeModalAlertPanel alloc] initWithPanel:a parent:parent];
|
||||
[cm run];
|
||||
[cm release];
|
||||
[a release];
|
||||
}
|
||||
|
||||
void uiMsgBox(uiWindow *parent, const char *title, const char *description)
|
||||
{
|
||||
msgbox(windowWindow(parent), title, description, NSInformationalAlertStyle);
|
||||
}
|
||||
|
||||
void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
|
||||
{
|
||||
msgbox(windowWindow(parent), title, description, NSCriticalAlertStyle);
|
||||
}
|
||||
292
dep/libui/darwin/tab.m
Normal file
292
dep/libui/darwin/tab.m
Normal file
@@ -0,0 +1,292 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO need to jiggle on tab change too (second page disabled tab label initially ambiguous)
|
||||
|
||||
@interface tabPage : NSObject {
|
||||
uiprivSingleChildConstraints constraints;
|
||||
int margined;
|
||||
NSView *view; // the NSTabViewItem view itself
|
||||
NSObject *pageID;
|
||||
}
|
||||
@property uiControl *c;
|
||||
@property NSLayoutPriority oldHorzHuggingPri;
|
||||
@property NSLayoutPriority oldVertHuggingPri;
|
||||
- (id)initWithView:(NSView *)v pageID:(NSObject *)o;
|
||||
- (NSView *)childView;
|
||||
- (void)establishChildConstraints;
|
||||
- (void)removeChildConstraints;
|
||||
- (int)isMargined;
|
||||
- (void)setMargined:(int)m;
|
||||
@end
|
||||
|
||||
struct uiTab {
|
||||
uiDarwinControl c;
|
||||
NSTabView *tabview;
|
||||
NSMutableArray *pages;
|
||||
NSLayoutPriority horzHuggingPri;
|
||||
NSLayoutPriority vertHuggingPri;
|
||||
};
|
||||
|
||||
@implementation tabPage
|
||||
|
||||
- (id)initWithView:(NSView *)v pageID:(NSObject *)o
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
self->view = [v retain];
|
||||
self->pageID = [o retain];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self removeChildConstraints];
|
||||
[self->view release];
|
||||
[self->pageID release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSView *)childView
|
||||
{
|
||||
return (NSView *) uiControlHandle(self.c);
|
||||
}
|
||||
|
||||
- (void)establishChildConstraints
|
||||
{
|
||||
[self removeChildConstraints];
|
||||
if (self.c == NULL)
|
||||
return;
|
||||
uiprivSingleChildConstraintsEstablish(&(self->constraints),
|
||||
self->view, [self childView],
|
||||
uiDarwinControlHugsTrailingEdge(uiDarwinControl(self.c)),
|
||||
uiDarwinControlHugsBottom(uiDarwinControl(self.c)),
|
||||
self->margined,
|
||||
@"uiTab page");
|
||||
}
|
||||
|
||||
- (void)removeChildConstraints
|
||||
{
|
||||
uiprivSingleChildConstraintsRemove(&(self->constraints), self->view);
|
||||
}
|
||||
|
||||
- (int)isMargined
|
||||
{
|
||||
return self->margined;
|
||||
}
|
||||
|
||||
- (void)setMargined:(int)m
|
||||
{
|
||||
self->margined = m;
|
||||
uiprivSingleChildConstraintsSetMargined(&(self->constraints), self->margined);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static void uiTabDestroy(uiControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
tabPage *page;
|
||||
|
||||
// first remove all tab pages so we can destroy all the children
|
||||
while ([t->tabview numberOfTabViewItems] != 0)
|
||||
[t->tabview removeTabViewItem:[t->tabview tabViewItemAtIndex:0]];
|
||||
// then destroy all the children
|
||||
for (page in t->pages) {
|
||||
[page removeChildConstraints];
|
||||
uiControlSetParent(page.c, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(page.c), nil);
|
||||
uiControlDestroy(page.c);
|
||||
}
|
||||
// and finally destroy ourselves
|
||||
[t->pages release];
|
||||
[t->tabview release];
|
||||
uiFreeControl(uiControl(t));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiTab, tabview)
|
||||
uiDarwinControlDefaultParent(uiTab, tabview)
|
||||
uiDarwinControlDefaultSetParent(uiTab, tabview)
|
||||
uiDarwinControlDefaultToplevel(uiTab, tabview)
|
||||
uiDarwinControlDefaultVisible(uiTab, tabview)
|
||||
uiDarwinControlDefaultShow(uiTab, tabview)
|
||||
uiDarwinControlDefaultHide(uiTab, tabview)
|
||||
uiDarwinControlDefaultEnabled(uiTab, tabview)
|
||||
uiDarwinControlDefaultEnable(uiTab, tabview)
|
||||
uiDarwinControlDefaultDisable(uiTab, tabview)
|
||||
|
||||
static void uiTabSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
tabPage *page;
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(t), enabled))
|
||||
return;
|
||||
for (page in t->pages)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(page.c), enabled);
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultSetSuperview(uiTab, tabview)
|
||||
|
||||
static void tabRelayout(uiTab *t)
|
||||
{
|
||||
tabPage *page;
|
||||
|
||||
for (page in t->pages)
|
||||
[page establishChildConstraints];
|
||||
// and this gets rid of some weird issues with regards to box alignment
|
||||
uiprivJiggleViewLayout(t->tabview);
|
||||
}
|
||||
|
||||
BOOL uiTabHugsTrailingEdge(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
return t->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
BOOL uiTabHugsBottom(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
return t->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;
|
||||
}
|
||||
|
||||
static void uiTabChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
static NSLayoutPriority uiTabHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
return t->horzHuggingPri;
|
||||
return t->vertHuggingPri;
|
||||
}
|
||||
|
||||
static void uiTabSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
if (orientation == NSLayoutConstraintOrientationHorizontal)
|
||||
t->horzHuggingPri = priority;
|
||||
else
|
||||
t->vertHuggingPri = priority;
|
||||
uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(t));
|
||||
}
|
||||
|
||||
static void uiTabChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiTab *t = uiTab(c);
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
void uiTabAppend(uiTab *t, const char *name, uiControl *child)
|
||||
{
|
||||
uiTabInsertAt(t, name, [t->pages count], child);
|
||||
}
|
||||
|
||||
void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)
|
||||
{
|
||||
tabPage *page;
|
||||
NSView *view;
|
||||
NSTabViewItem *i;
|
||||
NSObject *pageID;
|
||||
|
||||
uiControlSetParent(child, uiControl(t));
|
||||
|
||||
view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
|
||||
// note: if we turn off the autoresizing mask, nothing shows up
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(child), view);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(child), uiControlEnabledToUser(uiControl(t)));
|
||||
|
||||
// the documentation says these can be nil but the headers say these must not be; let's be safe and make them non-nil anyway
|
||||
pageID = [NSObject new];
|
||||
page = [[[tabPage alloc] initWithView:view pageID:pageID] autorelease];
|
||||
page.c = child;
|
||||
|
||||
// don't hug, just in case we're a stretchy tab
|
||||
page.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationHorizontal);
|
||||
page.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationVertical);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
|
||||
|
||||
[t->pages insertObject:page atIndex:n];
|
||||
|
||||
i = [[[NSTabViewItem alloc] initWithIdentifier:pageID] autorelease];
|
||||
[i setLabel:uiprivToNSString(name)];
|
||||
[i setView:view];
|
||||
[t->tabview insertTabViewItem:i atIndex:n];
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
void uiTabDelete(uiTab *t, int n)
|
||||
{
|
||||
tabPage *page;
|
||||
uiControl *child;
|
||||
NSTabViewItem *i;
|
||||
|
||||
page = (tabPage *) [t->pages objectAtIndex:n];
|
||||
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
|
||||
uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
|
||||
|
||||
child = page.c;
|
||||
[page removeChildConstraints];
|
||||
[t->pages removeObjectAtIndex:n];
|
||||
|
||||
uiControlSetParent(child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(child), nil);
|
||||
|
||||
i = [t->tabview tabViewItemAtIndex:n];
|
||||
[t->tabview removeTabViewItem:i];
|
||||
|
||||
tabRelayout(t);
|
||||
}
|
||||
|
||||
int uiTabNumPages(uiTab *t)
|
||||
{
|
||||
return [t->pages count];
|
||||
}
|
||||
|
||||
int uiTabMargined(uiTab *t, int n)
|
||||
{
|
||||
tabPage *page;
|
||||
|
||||
page = (tabPage *) [t->pages objectAtIndex:n];
|
||||
return [page isMargined];
|
||||
}
|
||||
|
||||
void uiTabSetMargined(uiTab *t, int n, int margined)
|
||||
{
|
||||
tabPage *page;
|
||||
|
||||
page = (tabPage *) [t->pages objectAtIndex:n];
|
||||
[page setMargined:margined];
|
||||
}
|
||||
|
||||
uiTab *uiNewTab(void)
|
||||
{
|
||||
uiTab *t;
|
||||
|
||||
uiDarwinNewControl(uiTab, t);
|
||||
|
||||
t->tabview = [[NSTabView alloc] initWithFrame:NSZeroRect];
|
||||
// also good for NSTabView (same selector and everything)
|
||||
uiDarwinSetControlFont((NSControl *) (t->tabview), NSRegularControlSize);
|
||||
|
||||
t->pages = [NSMutableArray new];
|
||||
|
||||
// default to low hugging to not hug edges
|
||||
t->horzHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
t->vertHuggingPri = NSLayoutPriorityDefaultLow;
|
||||
|
||||
return t;
|
||||
}
|
||||
27
dep/libui/darwin/table.h
Normal file
27
dep/libui/darwin/table.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// 3 june 2018
|
||||
#import "../common/table.h"
|
||||
|
||||
// table.m
|
||||
// TODO get rid of forward declaration
|
||||
@class uiprivTableModel;
|
||||
struct uiTableModel {
|
||||
uiTableModelHandler *mh;
|
||||
uiprivTableModel *m;
|
||||
NSMutableArray *tables;
|
||||
};
|
||||
struct uiTable {
|
||||
uiDarwinControl c;
|
||||
NSScrollView *sv;
|
||||
NSTableView *tv;
|
||||
uiprivScrollViewData *d;
|
||||
int backgroundColumn;
|
||||
uiTableModel *m;
|
||||
};
|
||||
|
||||
// tablecolumn.m
|
||||
@interface uiprivTableCellView : NSTableCellView
|
||||
- (void)uiprivUpdate:(NSInteger)row;
|
||||
@end
|
||||
@interface uiprivTableColumn : NSTableColumn
|
||||
- (uiprivTableCellView *)uiprivMakeCellView;
|
||||
@end
|
||||
218
dep/libui/darwin/table.m
Normal file
218
dep/libui/darwin/table.m
Normal file
@@ -0,0 +1,218 @@
|
||||
// 3 june 2018
|
||||
#import "uipriv_darwin.h"
|
||||
#import "table.h"
|
||||
|
||||
// TODO is the initial scroll position still wrong?
|
||||
|
||||
@interface uiprivTableModel : NSObject<NSTableViewDataSource, NSTableViewDelegate> {
|
||||
uiTableModel *m;
|
||||
}
|
||||
- (id)initWithModel:(uiTableModel *)model;
|
||||
@end
|
||||
|
||||
// TODO we really need to clean up the sharing of the table and model variables...
|
||||
@interface uiprivTableView : NSTableView {
|
||||
uiTable *uiprivT;
|
||||
uiTableModel *uiprivM;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m;
|
||||
@end
|
||||
|
||||
@implementation uiprivTableView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r uiprivT:(uiTable *)t uiprivM:(uiTableModel *)m
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->uiprivT = t;
|
||||
self->uiprivM = m;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// TODO is this correct for overflow scrolling?
|
||||
static void setBackgroundColor(uiprivTableView *t, NSTableRowView *rv, NSInteger row)
|
||||
{
|
||||
NSColor *color;
|
||||
double r, g, b, a;
|
||||
|
||||
if (t->uiprivT->backgroundColumn == -1)
|
||||
return; // let Cocoa do its default thing
|
||||
if (uiprivTableModelColorIfProvided(t->uiprivM, row, t->uiprivT->backgroundColumn, &r, &g, &b, &a))
|
||||
color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];
|
||||
else {
|
||||
NSArray *colors;
|
||||
NSInteger index;
|
||||
|
||||
// this usage is primarily a guess; hopefully it is correct for the non-two color case... (TODO)
|
||||
// it does seem to be correct for the two-color case, judging from comparing against the value of backgroundColor before changing it (and no, nil does not work; it just sets to white)
|
||||
colors = [NSColor controlAlternatingRowBackgroundColors];
|
||||
index = row % [colors count];
|
||||
color = (NSColor *) [colors objectAtIndex:index];
|
||||
}
|
||||
[rv setBackgroundColor:color];
|
||||
// color is autoreleased in all cases
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation uiprivTableModel
|
||||
|
||||
- (id)initWithModel:(uiTableModel *)model
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->m = model;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tv
|
||||
{
|
||||
return uiprivTableModelNumRows(self->m);
|
||||
}
|
||||
|
||||
- (NSView *)tableView:(NSTableView *)tv viewForTableColumn:(NSTableColumn *)cc row:(NSInteger)row
|
||||
{
|
||||
uiprivTableColumn *c = (uiprivTableColumn *) cc;
|
||||
uiprivTableCellView *cv;
|
||||
|
||||
cv = (uiprivTableCellView *) [tv makeViewWithIdentifier:[c identifier] owner:self];
|
||||
if (cv == nil)
|
||||
cv = [c uiprivMakeCellView];
|
||||
[cv uiprivUpdate:row];
|
||||
return cv;
|
||||
}
|
||||
|
||||
- (void)tableView:(NSTableView *)tv didAddRowView:(NSTableRowView *)rv forRow:(NSInteger)row
|
||||
{
|
||||
setBackgroundColor((uiprivTableView *) tv, rv, row);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
uiTableModel *uiNewTableModel(uiTableModelHandler *mh)
|
||||
{
|
||||
uiTableModel *m;
|
||||
|
||||
m = uiprivNew(uiTableModel);
|
||||
m->mh = mh;
|
||||
m->m = [[uiprivTableModel alloc] initWithModel:m];
|
||||
m->tables = [NSMutableArray new];
|
||||
return m;
|
||||
}
|
||||
|
||||
void uiFreeTableModel(uiTableModel *m)
|
||||
{
|
||||
if ([m->tables count] != 0)
|
||||
uiprivUserBug("You cannot free a uiTableModel while uiTables are using it.");
|
||||
[m->tables release];
|
||||
[m->m release];
|
||||
uiprivFree(m);
|
||||
}
|
||||
|
||||
void uiTableModelRowInserted(uiTableModel *m, int newIndex)
|
||||
{
|
||||
NSTableView *tv;
|
||||
NSIndexSet *set;
|
||||
|
||||
set = [NSIndexSet indexSetWithIndex:newIndex];
|
||||
for (tv in m->tables)
|
||||
[tv insertRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone];
|
||||
// set is autoreleased
|
||||
}
|
||||
|
||||
void uiTableModelRowChanged(uiTableModel *m, int index)
|
||||
{
|
||||
uiprivTableView *tv;
|
||||
NSTableRowView *rv;
|
||||
NSUInteger i, n;
|
||||
uiprivTableCellView *cv;
|
||||
|
||||
for (tv in m->tables) {
|
||||
rv = [tv rowViewAtRow:index makeIfNecessary:NO];
|
||||
if (rv != nil)
|
||||
setBackgroundColor(tv, rv, index);
|
||||
n = [[tv tableColumns] count];
|
||||
for (i = 0; i < n; i++) {
|
||||
cv = (uiprivTableCellView *) [tv viewAtColumn:i row:index makeIfNecessary:NO];
|
||||
if (cv != nil)
|
||||
[cv uiprivUpdate:index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uiTableModelRowDeleted(uiTableModel *m, int oldIndex)
|
||||
{
|
||||
NSTableView *tv;
|
||||
NSIndexSet *set;
|
||||
|
||||
set = [NSIndexSet indexSetWithIndex:oldIndex];
|
||||
for (tv in m->tables)
|
||||
[tv removeRowsAtIndexes:set withAnimation:NSTableViewAnimationEffectNone];
|
||||
// set is autoreleased
|
||||
}
|
||||
|
||||
uiTableModelHandler *uiprivTableModelHandler(uiTableModel *m)
|
||||
{
|
||||
return m->mh;
|
||||
}
|
||||
|
||||
uiDarwinControlAllDefaultsExceptDestroy(uiTable, sv)
|
||||
|
||||
static void uiTableDestroy(uiControl *c)
|
||||
{
|
||||
uiTable *t = uiTable(c);
|
||||
|
||||
[t->m->tables removeObject:t->tv];
|
||||
uiprivScrollViewFreeData(t->sv, t->d);
|
||||
[t->tv release];
|
||||
[t->sv release];
|
||||
uiFreeControl(uiControl(t));
|
||||
}
|
||||
|
||||
uiTable *uiNewTable(uiTableParams *p)
|
||||
{
|
||||
uiTable *t;
|
||||
uiprivScrollViewCreateParams sp;
|
||||
|
||||
uiDarwinNewControl(uiTable, t);
|
||||
t->m = p->Model;
|
||||
t->backgroundColumn = p->RowBackgroundColorModelColumn;
|
||||
|
||||
t->tv = [[uiprivTableView alloc] initWithFrame:NSZeroRect uiprivT:t uiprivM:t->m];
|
||||
|
||||
[t->tv setDataSource:t->m->m];
|
||||
[t->tv setDelegate:t->m->m];
|
||||
[t->tv reloadData];
|
||||
[t->m->tables addObject:t->tv];
|
||||
|
||||
// TODO is this sufficient?
|
||||
[t->tv setAllowsColumnReordering:NO];
|
||||
[t->tv setAllowsColumnResizing:YES];
|
||||
[t->tv setAllowsMultipleSelection:NO];
|
||||
[t->tv setAllowsEmptySelection:YES];
|
||||
[t->tv setAllowsColumnSelection:NO];
|
||||
[t->tv setUsesAlternatingRowBackgroundColors:YES];
|
||||
[t->tv setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleRegular];
|
||||
[t->tv setGridStyleMask:NSTableViewGridNone];
|
||||
[t->tv setAllowsTypeSelect:YES];
|
||||
// TODO floatsGroupRows — do we even allow group rows?
|
||||
|
||||
memset(&sp, 0, sizeof (uiprivScrollViewCreateParams));
|
||||
sp.DocumentView = t->tv;
|
||||
// this is what Interface Builder sets it to
|
||||
// TODO verify
|
||||
sp.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
|
||||
sp.DrawsBackground = YES;
|
||||
sp.Bordered = YES;
|
||||
sp.HScroll = YES;
|
||||
sp.VScroll = YES;
|
||||
t->sv = uiprivMkScrollView(&sp, &(t->d));
|
||||
|
||||
// TODO WHY DOES THIS REMOVE ALL GRAPHICAL GLITCHES?
|
||||
// I got the idea from http://jwilling.com/blog/optimized-nstableview-scrolling/ but that was on an unrelated problem I didn't seem to have (although I have small-ish tables to start with)
|
||||
// I don't get layer-backing... am I supposed to layer-back EVERYTHING manually? I need to check Interface Builder again...
|
||||
[t->sv setWantsLayer:YES];
|
||||
|
||||
return t;
|
||||
}
|
||||
720
dep/libui/darwin/tablecolumn.m
Normal file
720
dep/libui/darwin/tablecolumn.m
Normal file
@@ -0,0 +1,720 @@
|
||||
// 3 june 2018
|
||||
#import "uipriv_darwin.h"
|
||||
#import "table.h"
|
||||
|
||||
// values from interface builder
|
||||
#define textColumnLeading 2
|
||||
#define textColumnTrailing 2
|
||||
#define imageColumnLeading 3
|
||||
#define imageTextColumnLeading 7
|
||||
#define checkboxTextColumnLeading 0
|
||||
// these aren't provided by IB; let's just choose one
|
||||
#define checkboxColumnLeading imageColumnLeading
|
||||
#define progressBarColumnLeading imageColumnLeading
|
||||
#define progressBarColumnTrailing progressBarColumnLeading
|
||||
#define buttonColumnLeading imageColumnLeading
|
||||
#define buttonColumnTrailing buttonColumnLeading
|
||||
|
||||
@implementation uiprivTableCellView
|
||||
|
||||
- (void)uiprivUpdate:(NSInteger)row
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation uiprivTableColumn
|
||||
|
||||
- (uiprivTableCellView *)uiprivMakeCellView
|
||||
{
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil; // appease compiler
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
struct textColumnCreateParams {
|
||||
uiTable *t;
|
||||
uiTableModel *m;
|
||||
|
||||
BOOL makeTextField;
|
||||
int textModelColumn;
|
||||
int textEditableModelColumn;
|
||||
uiTableTextColumnOptionalParams textParams;
|
||||
|
||||
BOOL makeImageView;
|
||||
int imageModelColumn;
|
||||
|
||||
BOOL makeCheckbox;
|
||||
int checkboxModelColumn;
|
||||
int checkboxEditableModelColumn;
|
||||
};
|
||||
|
||||
@interface uiprivTextImageCheckboxTableCellView : uiprivTableCellView {
|
||||
uiTable *t;
|
||||
uiTableModel *m;
|
||||
|
||||
NSTextField *tf;
|
||||
int textModelColumn;
|
||||
int textEditableModelColumn;
|
||||
uiTableTextColumnOptionalParams textParams;
|
||||
|
||||
NSImageView *iv;
|
||||
int imageModelColumn;
|
||||
|
||||
NSButton *cb;
|
||||
int checkboxModelColumn;
|
||||
int checkboxEditableModelColumn;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p;
|
||||
- (IBAction)uiprivOnTextFieldAction:(id)sender;
|
||||
- (IBAction)uiprivOnCheckboxAction:(id)sender;
|
||||
@end
|
||||
|
||||
@implementation uiprivTextImageCheckboxTableCellView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r params:(struct textColumnCreateParams *)p
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
NSMutableArray *constraints;
|
||||
|
||||
self->t = p->t;
|
||||
self->m = p->m;
|
||||
constraints = [NSMutableArray new];
|
||||
|
||||
self->tf = nil;
|
||||
if (p->makeTextField) {
|
||||
self->textModelColumn = p->textModelColumn;
|
||||
self->textEditableModelColumn = p->textEditableModelColumn;
|
||||
self->textParams = p->textParams;
|
||||
|
||||
self->tf = uiprivNewLabel(@"");
|
||||
// TODO set wrap and ellipsize modes?
|
||||
[self->tf setTarget:self];
|
||||
[self->tf setAction:@selector(uiprivOnTextFieldAction:)];
|
||||
[self->tf setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self->tf];
|
||||
|
||||
// TODO for all three controls: set hugging and compression resistance properly
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self->tf, NSLayoutAttributeLeading,
|
||||
1, -textColumnLeading,
|
||||
@"uiTable cell text leading constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self->tf, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiTable cell text top constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->tf, NSLayoutAttributeTrailing,
|
||||
1, textColumnTrailing,
|
||||
@"uiTable cell text trailing constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self->tf, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiTable cell text bottom constraint")];
|
||||
}
|
||||
|
||||
self->iv = nil;
|
||||
if (p->makeImageView) {
|
||||
self->imageModelColumn = p->imageModelColumn;
|
||||
|
||||
self->iv = [[NSImageView alloc] initWithFrame:NSZeroRect];
|
||||
[self->iv setImageFrameStyle:NSImageFrameNone];
|
||||
[self->iv setImageAlignment:NSImageAlignCenter];
|
||||
[self->iv setImageScaling:NSImageScaleProportionallyDown];
|
||||
[self->iv setAnimates:NO];
|
||||
[self->iv setEditable:NO];
|
||||
[self->iv setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self->iv];
|
||||
|
||||
[constraints addObject:uiprivMkConstraint(self->iv, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
self->iv, NSLayoutAttributeHeight,
|
||||
1, 0,
|
||||
@"uiTable image squareness constraint")];
|
||||
if (self->tf != nil) {
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self->iv, NSLayoutAttributeLeading,
|
||||
1, -imageColumnLeading,
|
||||
@"uiTable cell image leading constraint")];
|
||||
[constraints replaceObjectAtIndex:0
|
||||
withObject:uiprivMkConstraint(self->iv, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->tf, NSLayoutAttributeLeading,
|
||||
1, -imageTextColumnLeading,
|
||||
@"uiTable cell image-text spacing constraint")];
|
||||
} else
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX,
|
||||
NSLayoutRelationEqual,
|
||||
self->iv, NSLayoutAttributeCenterX,
|
||||
1, 0,
|
||||
@"uiTable cell image centering constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self->iv, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiTable cell image top constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self->iv, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiTable cell image bottom constraint")];
|
||||
}
|
||||
|
||||
self->cb = nil;
|
||||
if (p->makeCheckbox) {
|
||||
self->checkboxModelColumn = p->checkboxModelColumn;
|
||||
self->checkboxEditableModelColumn = p->checkboxEditableModelColumn;
|
||||
|
||||
self->cb = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[self->cb setTitle:@""];
|
||||
[self->cb setButtonType:NSSwitchButton];
|
||||
// doesn't seem to have an associated bezel style
|
||||
[self->cb setBordered:NO];
|
||||
[self->cb setTransparent:NO];
|
||||
uiDarwinSetControlFont(self->cb, NSRegularControlSize);
|
||||
[self->cb setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self->cb];
|
||||
|
||||
if (self->tf != nil) {
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self->cb, NSLayoutAttributeLeading,
|
||||
1, -imageColumnLeading,
|
||||
@"uiTable cell checkbox leading constraint")];
|
||||
[constraints replaceObjectAtIndex:0
|
||||
withObject:uiprivMkConstraint(self->cb, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->tf, NSLayoutAttributeLeading,
|
||||
1, -imageTextColumnLeading,
|
||||
@"uiTable cell checkbox-text spacing constraint")];
|
||||
} else
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeCenterX,
|
||||
NSLayoutRelationEqual,
|
||||
self->cb, NSLayoutAttributeCenterX,
|
||||
1, 0,
|
||||
@"uiTable cell checkbox centering constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self->cb, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiTable cell checkbox top constraint")];
|
||||
[constraints addObject:uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self->cb, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiTable cell checkbox bottom constraint")];
|
||||
}
|
||||
|
||||
[self addConstraints:constraints];
|
||||
|
||||
// take advantage of NSTableCellView-provided accessibility features
|
||||
if (self->tf != nil)
|
||||
[self setTextField:self->tf];
|
||||
if (self->iv != nil)
|
||||
[self setImageView:self->iv];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (self->cb != nil) {
|
||||
[self->cb release];
|
||||
self->cb = nil;
|
||||
}
|
||||
if (self->iv != nil) {
|
||||
[self->iv release];
|
||||
self->iv = nil;
|
||||
}
|
||||
if (self->tf != nil) {
|
||||
[self->tf release];
|
||||
self->tf = nil;
|
||||
}
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)uiprivUpdate:(NSInteger)row
|
||||
{
|
||||
uiTableValue *value;
|
||||
|
||||
if (self->tf != nil) {
|
||||
NSString *str;
|
||||
NSColor *color;
|
||||
double r, g, b, a;
|
||||
|
||||
value = uiprivTableModelCellValue(self->m, row, self->textModelColumn);
|
||||
str = uiprivToNSString(uiTableValueString(value));
|
||||
uiFreeTableValue(value);
|
||||
[self->tf setStringValue:str];
|
||||
|
||||
[self->tf setEditable:uiprivTableModelCellEditable(self->m, row, self->textEditableModelColumn)];
|
||||
|
||||
color = [NSColor controlTextColor];
|
||||
if (uiprivTableModelColorIfProvided(self->m, row, self->textParams.ColorModelColumn, &r, &g, &b, &a))
|
||||
color = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];
|
||||
[self->tf setTextColor:color];
|
||||
// we don't own color in ether case; don't release
|
||||
}
|
||||
if (self->iv != nil) {
|
||||
uiImage *img;
|
||||
|
||||
value = uiprivTableModelCellValue(self->m, row, self->imageModelColumn);
|
||||
img = uiTableValueImage(value);
|
||||
uiFreeTableValue(value);
|
||||
[self->iv setImage:uiprivImageNSImage(img)];
|
||||
}
|
||||
if (self->cb != nil) {
|
||||
value = uiprivTableModelCellValue(self->m, row, self->checkboxModelColumn);
|
||||
if (uiTableValueInt(value) != 0)
|
||||
[self->cb setState:NSOnState];
|
||||
else
|
||||
[self->cb setState:NSOffState];
|
||||
uiFreeTableValue(value);
|
||||
|
||||
[self->cb setEnabled:uiprivTableModelCellEditable(self->m, row, self->checkboxEditableModelColumn)];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)uiprivOnTextFieldAction:(id)sender
|
||||
{
|
||||
NSInteger row;
|
||||
uiTableValue *value;
|
||||
|
||||
row = [self->t->tv rowForView:self->tf];
|
||||
value = uiNewTableValueString([[self->tf stringValue] UTF8String]);
|
||||
uiprivTableModelSetCellValue(self->m, row, self->textModelColumn, value);
|
||||
uiFreeTableValue(value);
|
||||
// always refresh the value in case the model rejected it
|
||||
// TODO document that we do this, but not for the whole row (or decide to do both, or do neither...)
|
||||
[self uiprivUpdate:row];
|
||||
}
|
||||
|
||||
- (IBAction)uiprivOnCheckboxAction:(id)sender
|
||||
{
|
||||
NSInteger row;
|
||||
uiTableValue *value;
|
||||
|
||||
row = [self->t->tv rowForView:self->cb];
|
||||
value = uiNewTableValueInt([self->cb state] != NSOffState);
|
||||
uiprivTableModelSetCellValue(self->m, row, self->checkboxModelColumn, value);
|
||||
uiFreeTableValue(value);
|
||||
// always refresh the value in case the model rejected it
|
||||
[self uiprivUpdate:row];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface uiprivTextImageCheckboxTableColumn : uiprivTableColumn {
|
||||
struct textColumnCreateParams params;
|
||||
}
|
||||
- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p;
|
||||
@end
|
||||
|
||||
@implementation uiprivTextImageCheckboxTableColumn
|
||||
|
||||
- (id)initWithIdentifier:(NSString *)ident params:(struct textColumnCreateParams *)p
|
||||
{
|
||||
self = [super initWithIdentifier:ident];
|
||||
if (self)
|
||||
self->params = *p;
|
||||
return self;
|
||||
}
|
||||
|
||||
- (uiprivTableCellView *)uiprivMakeCellView
|
||||
{
|
||||
uiprivTableCellView *cv;
|
||||
|
||||
cv = [[uiprivTextImageCheckboxTableCellView alloc] initWithFrame:NSZeroRect params:&(self->params)];
|
||||
[cv setIdentifier:[self identifier]];
|
||||
return cv;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface uiprivProgressBarTableCellView : uiprivTableCellView {
|
||||
uiTable *t;
|
||||
uiTableModel *m;
|
||||
NSProgressIndicator *p;
|
||||
int modelColumn;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc;
|
||||
@end
|
||||
|
||||
@implementation uiprivProgressBarTableCellView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->t = table;
|
||||
self->m = model;
|
||||
self->modelColumn = mc;
|
||||
|
||||
self->p = [[NSProgressIndicator alloc] initWithFrame:NSZeroRect];
|
||||
[self->p setControlSize:NSRegularControlSize];
|
||||
[self->p setBezeled:YES];
|
||||
[self->p setStyle:NSProgressIndicatorBarStyle];
|
||||
[self->p setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self->p];
|
||||
|
||||
// TODO set hugging and compression resistance properly
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self->p, NSLayoutAttributeLeading,
|
||||
1, -progressBarColumnLeading,
|
||||
@"uiTable cell progressbar leading constraint")];
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self->p, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiTable cell progressbar top constraint")];
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->p, NSLayoutAttributeTrailing,
|
||||
1, progressBarColumnTrailing,
|
||||
@"uiTable cell progressbar trailing constraint")];
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self->p, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiTable cell progressbar bottom constraint")];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self->p release];
|
||||
self->p = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)uiprivUpdate:(NSInteger)row
|
||||
{
|
||||
uiTableValue *value;
|
||||
int progress;
|
||||
|
||||
value = uiprivTableModelCellValue(self->m, row, self->modelColumn);
|
||||
progress = uiTableValueInt(value);
|
||||
uiFreeTableValue(value);
|
||||
if (progress == -1) {
|
||||
[self->p setIndeterminate:YES];
|
||||
[self->p startAnimation:self->p];
|
||||
} else if (progress == 100) {
|
||||
[self->p setIndeterminate:NO];
|
||||
[self->p setMaxValue:101];
|
||||
[self->p setDoubleValue:101];
|
||||
[self->p setDoubleValue:100];
|
||||
[self->p setMaxValue:100];
|
||||
} else {
|
||||
[self->p setIndeterminate:NO];
|
||||
[self->p setDoubleValue:(progress + 1)];
|
||||
[self->p setDoubleValue:progress];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface uiprivProgressBarTableColumn : uiprivTableColumn {
|
||||
uiTable *t;
|
||||
// TODO remove the need for this given t (or make t not require m, one of the two)
|
||||
uiTableModel *m;
|
||||
int modelColumn;
|
||||
}
|
||||
- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc;
|
||||
@end
|
||||
|
||||
@implementation uiprivProgressBarTableColumn
|
||||
|
||||
- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc
|
||||
{
|
||||
self = [super initWithIdentifier:ident];
|
||||
if (self) {
|
||||
self->t = table;
|
||||
self->m = model;
|
||||
self->modelColumn = mc;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (uiprivTableCellView *)uiprivMakeCellView
|
||||
{
|
||||
uiprivTableCellView *cv;
|
||||
|
||||
cv = [[uiprivProgressBarTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn];
|
||||
[cv setIdentifier:[self identifier]];
|
||||
return cv;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface uiprivButtonTableCellView : uiprivTableCellView {
|
||||
uiTable *t;
|
||||
uiTableModel *m;
|
||||
NSButton *b;
|
||||
int modelColumn;
|
||||
int editableColumn;
|
||||
}
|
||||
- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec;
|
||||
- (IBAction)uiprivOnClicked:(id)sender;
|
||||
@end
|
||||
|
||||
@implementation uiprivButtonTableCellView
|
||||
|
||||
- (id)initWithFrame:(NSRect)r table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec
|
||||
{
|
||||
self = [super initWithFrame:r];
|
||||
if (self) {
|
||||
self->t = table;
|
||||
self->m = model;
|
||||
self->modelColumn = mc;
|
||||
self->editableColumn = ec;
|
||||
|
||||
self->b = [[NSButton alloc] initWithFrame:NSZeroRect];
|
||||
[self->b setButtonType:NSMomentaryPushInButton];
|
||||
[self->b setBordered:YES];
|
||||
[self->b setBezelStyle:NSRoundRectBezelStyle];
|
||||
uiDarwinSetControlFont(self->b, NSRegularControlSize);
|
||||
[self->b setTarget:self];
|
||||
[self->b setAction:@selector(uiprivOnClicked:)];
|
||||
[self->b setTranslatesAutoresizingMaskIntoConstraints:NO];
|
||||
[self addSubview:self->b];
|
||||
|
||||
// TODO set hugging and compression resistance properly
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeLeading,
|
||||
NSLayoutRelationEqual,
|
||||
self->b, NSLayoutAttributeLeading,
|
||||
1, -buttonColumnLeading,
|
||||
@"uiTable cell button leading constraint")];
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTop,
|
||||
NSLayoutRelationEqual,
|
||||
self->b, NSLayoutAttributeTop,
|
||||
1, 0,
|
||||
@"uiTable cell button top constraint")];
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeTrailing,
|
||||
NSLayoutRelationEqual,
|
||||
self->b, NSLayoutAttributeTrailing,
|
||||
1, buttonColumnTrailing,
|
||||
@"uiTable cell button trailing constraint")];
|
||||
[self addConstraint:uiprivMkConstraint(self, NSLayoutAttributeBottom,
|
||||
NSLayoutRelationEqual,
|
||||
self->b, NSLayoutAttributeBottom,
|
||||
1, 0,
|
||||
@"uiTable cell button bottom constraint")];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self->b release];
|
||||
self->b = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)uiprivUpdate:(NSInteger)row
|
||||
{
|
||||
uiTableValue *value;
|
||||
NSString *str;
|
||||
|
||||
value = uiprivTableModelCellValue(self->m, row, self->modelColumn);
|
||||
str = uiprivToNSString(uiTableValueString(value));
|
||||
uiFreeTableValue(value);
|
||||
[self->b setTitle:str];
|
||||
|
||||
[self->b setEnabled:uiprivTableModelCellEditable(self->m, row, self->editableColumn)];
|
||||
}
|
||||
|
||||
- (IBAction)uiprivOnClicked:(id)sender
|
||||
{
|
||||
NSInteger row;
|
||||
|
||||
row = [self->t->tv rowForView:self->b];
|
||||
uiprivTableModelSetCellValue(self->m, row, self->modelColumn, NULL);
|
||||
// TODO document we DON'T update the cell after doing this
|
||||
// TODO or decide what to do instead
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface uiprivButtonTableColumn : uiprivTableColumn {
|
||||
uiTable *t;
|
||||
uiTableModel *m;
|
||||
int modelColumn;
|
||||
int editableColumn;
|
||||
}
|
||||
- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec;
|
||||
@end
|
||||
|
||||
@implementation uiprivButtonTableColumn
|
||||
|
||||
- (id)initWithIdentifier:(NSString *)ident table:(uiTable *)table model:(uiTableModel *)model modelColumn:(int)mc editableColumn:(int)ec
|
||||
{
|
||||
self = [super initWithIdentifier:ident];
|
||||
if (self) {
|
||||
self->t = table;
|
||||
self->m = model;
|
||||
self->modelColumn = mc;
|
||||
self->editableColumn = ec;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (uiprivTableCellView *)uiprivMakeCellView
|
||||
{
|
||||
uiprivTableCellView *cv;
|
||||
|
||||
cv = [[uiprivButtonTableCellView alloc] initWithFrame:NSZeroRect table:self->t model:self->m modelColumn:self->modelColumn editableColumn:self->editableColumn];
|
||||
[cv setIdentifier:[self identifier]];
|
||||
return cv;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void uiTableAppendTextColumn(uiTable *t, const char *name, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
|
||||
{
|
||||
struct textColumnCreateParams p;
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
memset(&p, 0, sizeof (struct textColumnCreateParams));
|
||||
p.t = t;
|
||||
p.m = t->m;
|
||||
|
||||
p.makeTextField = YES;
|
||||
p.textModelColumn = textModelColumn;
|
||||
p.textEditableModelColumn = textEditableModelColumn;
|
||||
if (textParams != NULL)
|
||||
p.textParams = *textParams;
|
||||
else
|
||||
p.textParams = uiprivDefaultTextColumnOptionalParams;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
|
||||
void uiTableAppendImageColumn(uiTable *t, const char *name, int imageModelColumn)
|
||||
{
|
||||
struct textColumnCreateParams p;
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
memset(&p, 0, sizeof (struct textColumnCreateParams));
|
||||
p.t = t;
|
||||
p.m = t->m;
|
||||
|
||||
p.makeImageView = YES;
|
||||
p.imageModelColumn = imageModelColumn;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
|
||||
void uiTableAppendImageTextColumn(uiTable *t, const char *name, int imageModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
|
||||
{
|
||||
struct textColumnCreateParams p;
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
memset(&p, 0, sizeof (struct textColumnCreateParams));
|
||||
p.t = t;
|
||||
p.m = t->m;
|
||||
|
||||
p.makeTextField = YES;
|
||||
p.textModelColumn = textModelColumn;
|
||||
p.textEditableModelColumn = textEditableModelColumn;
|
||||
if (textParams != NULL)
|
||||
p.textParams = *textParams;
|
||||
else
|
||||
p.textParams = uiprivDefaultTextColumnOptionalParams;
|
||||
|
||||
p.makeImageView = YES;
|
||||
p.imageModelColumn = imageModelColumn;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
|
||||
void uiTableAppendCheckboxColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn)
|
||||
{
|
||||
struct textColumnCreateParams p;
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
memset(&p, 0, sizeof (struct textColumnCreateParams));
|
||||
p.t = t;
|
||||
p.m = t->m;
|
||||
|
||||
p.makeCheckbox = YES;
|
||||
p.checkboxModelColumn = checkboxModelColumn;
|
||||
p.checkboxEditableModelColumn = checkboxEditableModelColumn;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
|
||||
void uiTableAppendCheckboxTextColumn(uiTable *t, const char *name, int checkboxModelColumn, int checkboxEditableModelColumn, int textModelColumn, int textEditableModelColumn, uiTableTextColumnOptionalParams *textParams)
|
||||
{
|
||||
struct textColumnCreateParams p;
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
memset(&p, 0, sizeof (struct textColumnCreateParams));
|
||||
p.t = t;
|
||||
p.m = t->m;
|
||||
|
||||
p.makeTextField = YES;
|
||||
p.textModelColumn = textModelColumn;
|
||||
p.textEditableModelColumn = textEditableModelColumn;
|
||||
if (textParams != NULL)
|
||||
p.textParams = *textParams;
|
||||
else
|
||||
p.textParams = uiprivDefaultTextColumnOptionalParams;
|
||||
|
||||
p.makeCheckbox = YES;
|
||||
p.checkboxModelColumn = checkboxModelColumn;
|
||||
p.checkboxEditableModelColumn = checkboxEditableModelColumn;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivTextImageCheckboxTableColumn alloc] initWithIdentifier:str params:&p];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
|
||||
void uiTableAppendProgressBarColumn(uiTable *t, const char *name, int progressModelColumn)
|
||||
{
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivProgressBarTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:progressModelColumn];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
|
||||
void uiTableAppendButtonColumn(uiTable *t, const char *name, int buttonModelColumn, int buttonClickableModelColumn)
|
||||
{
|
||||
uiprivTableColumn *col;
|
||||
NSString *str;
|
||||
|
||||
str = [NSString stringWithUTF8String:name];
|
||||
col = [[uiprivButtonTableColumn alloc] initWithIdentifier:str table:t model:t->m modelColumn:buttonModelColumn editableColumn:buttonClickableModelColumn];
|
||||
[col setTitle:str];
|
||||
[t->tv addTableColumn:col];
|
||||
}
|
||||
24
dep/libui/darwin/text.m
Normal file
24
dep/libui/darwin/text.m
Normal file
@@ -0,0 +1,24 @@
|
||||
// 10 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
char *uiDarwinNSStringToText(NSString *s)
|
||||
{
|
||||
char *out;
|
||||
|
||||
out = strdup([s UTF8String]);
|
||||
if (out == NULL) {
|
||||
fprintf(stderr, "memory exhausted in uiDarwinNSStringToText()\n");
|
||||
abort();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void uiFreeText(char *s)
|
||||
{
|
||||
free(s);
|
||||
}
|
||||
|
||||
int uiprivStricmp(const char *a, const char *b)
|
||||
{
|
||||
return strcasecmp(a, b);
|
||||
}
|
||||
163
dep/libui/darwin/uipriv_darwin.h
Normal file
163
dep/libui/darwin/uipriv_darwin.h
Normal file
@@ -0,0 +1,163 @@
|
||||
// 6 january 2015
|
||||
// note: as of OS X Sierra, the -mmacosx-version-min compiler options governs deprecation warnings; keep these around anyway just in case
|
||||
#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_8
|
||||
#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_8
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <dlfcn.h> // see future.m
|
||||
#import "../ui.h"
|
||||
#import "../ui_darwin.h"
|
||||
#import "../common/uipriv.h"
|
||||
|
||||
// TODO should we rename the uiprivMk things or not
|
||||
// TODO what about renaming class wrapper functions that return the underlying class (like uiprivNewLabel())
|
||||
|
||||
#if __has_feature(objc_arc)
|
||||
#error Sorry, libui cannot be compiled with ARC.
|
||||
#endif
|
||||
|
||||
#define uiprivToNSString(str) [NSString stringWithUTF8String:(str)]
|
||||
#define uiprivFromNSString(str) [(str) UTF8String]
|
||||
|
||||
// TODO find a better place for this
|
||||
#ifndef NSAppKitVersionNumber10_9
|
||||
#define NSAppKitVersionNumber10_9 1265
|
||||
#endif
|
||||
|
||||
// map.m
|
||||
typedef struct uiprivMap uiprivMap;
|
||||
extern uiprivMap *uiprivNewMap(void);
|
||||
extern void uiprivMapDestroy(uiprivMap *m);
|
||||
extern void *uiprivMapGet(uiprivMap *m, void *key);
|
||||
extern void uiprivMapSet(uiprivMap *m, void *key, void *value);
|
||||
extern void uiprivMapDelete(uiprivMap *m, void *key);
|
||||
extern void uiprivMapWalk(uiprivMap *m, void (*f)(void *key, void *value));
|
||||
extern void uiprivMapReset(uiprivMap *m);
|
||||
|
||||
// menu.m
|
||||
@interface uiprivMenuManager : NSObject {
|
||||
uiprivMap *items;
|
||||
BOOL hasQuit;
|
||||
BOOL hasPreferences;
|
||||
BOOL hasAbout;
|
||||
}
|
||||
@property (strong) NSMenuItem *quitItem;
|
||||
@property (strong) NSMenuItem *preferencesItem;
|
||||
@property (strong) NSMenuItem *aboutItem;
|
||||
// NSMenuValidation is only informal
|
||||
- (BOOL)validateMenuItem:(NSMenuItem *)item;
|
||||
- (NSMenu *)makeMenubar;
|
||||
@end
|
||||
extern void uiprivFinalizeMenus(void);
|
||||
extern void uiprivUninitMenus(void);
|
||||
|
||||
// main.m
|
||||
@interface uiprivApplicationClass : NSApplication
|
||||
@end
|
||||
// this is needed because NSApp is of type id, confusing clang
|
||||
#define uiprivNSApp() ((uiprivApplicationClass *) NSApp)
|
||||
@interface uiprivAppDelegate : NSObject<NSApplicationDelegate>
|
||||
@property (strong) uiprivMenuManager *menuManager;
|
||||
@end
|
||||
#define uiprivAppDelegate() ((uiprivAppDelegate *) [uiprivNSApp() delegate])
|
||||
typedef struct uiprivNextEventArgs uiprivNextEventArgs;
|
||||
struct uiprivNextEventArgs {
|
||||
NSEventMask mask;
|
||||
NSDate *duration;
|
||||
// LONGTERM no NSRunLoopMode?
|
||||
NSString *mode;
|
||||
BOOL dequeue;
|
||||
};
|
||||
extern int uiprivMainStep(uiprivNextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *));
|
||||
|
||||
// util.m
|
||||
extern void uiprivDisableAutocorrect(NSTextView *);
|
||||
|
||||
// entry.m
|
||||
extern void uiprivFinishNewTextField(NSTextField *, BOOL);
|
||||
extern NSTextField *uiprivNewEditableTextField(void);
|
||||
|
||||
// window.m
|
||||
@interface uiprivNSWindow : NSWindow
|
||||
- (void)uiprivDoMove:(NSEvent *)initialEvent;
|
||||
- (void)uiprivDoResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge;
|
||||
@end
|
||||
extern uiWindow *uiprivWindowFromNSWindow(NSWindow *);
|
||||
|
||||
// alloc.m
|
||||
extern NSMutableArray *uiprivDelegates;
|
||||
extern void uiprivInitAlloc(void);
|
||||
extern void uiprivUninitAlloc(void);
|
||||
|
||||
// autolayout.m
|
||||
extern NSLayoutConstraint *uiprivMkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc);
|
||||
extern void uiprivJiggleViewLayout(NSView *view);
|
||||
typedef struct uiprivSingleChildConstraints uiprivSingleChildConstraints;
|
||||
struct uiprivSingleChildConstraints {
|
||||
NSLayoutConstraint *leadingConstraint;
|
||||
NSLayoutConstraint *topConstraint;
|
||||
NSLayoutConstraint *trailingConstraintGreater;
|
||||
NSLayoutConstraint *trailingConstraintEqual;
|
||||
NSLayoutConstraint *bottomConstraintGreater;
|
||||
NSLayoutConstraint *bottomConstraintEqual;
|
||||
};
|
||||
extern void uiprivSingleChildConstraintsEstablish(uiprivSingleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc);
|
||||
extern void uiprivSingleChildConstraintsRemove(uiprivSingleChildConstraints *c, NSView *cv);
|
||||
extern void uiprivSingleChildConstraintsSetMargined(uiprivSingleChildConstraints *c, int margined);
|
||||
|
||||
// area.m
|
||||
extern int uiprivSendAreaEvents(NSEvent *);
|
||||
|
||||
// areaevents.m
|
||||
extern BOOL uiprivFromKeycode(unsigned short keycode, uiAreaKeyEvent *ke);
|
||||
extern BOOL uiprivKeycodeModifier(unsigned short keycode, uiModifiers *mod);
|
||||
|
||||
// draw.m
|
||||
extern uiDrawContext *uiprivDrawNewContext(CGContextRef, CGFloat);
|
||||
extern void uiprivDrawFreeContext(uiDrawContext *);
|
||||
|
||||
// fontbutton.m
|
||||
extern BOOL uiprivFontButtonInhibitSendAction(SEL sel, id from, id to);
|
||||
extern BOOL uiprivFontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override);
|
||||
extern void uiprivSetupFontPanel(void);
|
||||
|
||||
// colorbutton.m
|
||||
extern BOOL uiprivColorButtonInhibitSendAction(SEL sel, id from, id to);
|
||||
|
||||
// scrollview.m
|
||||
typedef struct uiprivScrollViewCreateParams uiprivScrollViewCreateParams;
|
||||
struct uiprivScrollViewCreateParams {
|
||||
// TODO MAYBE fix these identifiers
|
||||
NSView *DocumentView;
|
||||
NSColor *BackgroundColor;
|
||||
BOOL DrawsBackground;
|
||||
BOOL Bordered;
|
||||
BOOL HScroll;
|
||||
BOOL VScroll;
|
||||
};
|
||||
typedef struct uiprivScrollViewData uiprivScrollViewData;
|
||||
extern NSScrollView *uiprivMkScrollView(uiprivScrollViewCreateParams *p, uiprivScrollViewData **dout);
|
||||
extern void uiprivScrollViewSetScrolling(NSScrollView *sv, uiprivScrollViewData *d, BOOL hscroll, BOOL vscroll);
|
||||
extern void uiprivScrollViewFreeData(NSScrollView *sv, uiprivScrollViewData *d);
|
||||
|
||||
// label.m
|
||||
extern NSTextField *uiprivNewLabel(NSString *str);
|
||||
|
||||
// image.m
|
||||
extern NSImage *uiprivImageNSImage(uiImage *);
|
||||
|
||||
// winmoveresize.m
|
||||
extern void uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent);
|
||||
extern void uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge);
|
||||
|
||||
// future.m
|
||||
extern CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureTag;
|
||||
extern CFStringRef *uiprivFUTURE_kCTFontOpenTypeFeatureValue;
|
||||
extern CFStringRef *uiprivFUTURE_kCTBackgroundColorAttributeName;
|
||||
extern void uiprivLoadFutures(void);
|
||||
extern void uiprivFUTURE_NSLayoutConstraint_setIdentifier(NSLayoutConstraint *constraint, NSString *identifier);
|
||||
extern BOOL uiprivFUTURE_NSWindow_performWindowDragWithEvent(NSWindow *w, NSEvent *initialEvent);
|
||||
|
||||
// undocumented.m
|
||||
extern CFStringRef uiprivUNDOC_kCTFontPreferredSubFamilyNameKey;
|
||||
extern CFStringRef uiprivUNDOC_kCTFontPreferredFamilyNameKey;
|
||||
extern void uiprivLoadUndocumented(void);
|
||||
31
dep/libui/darwin/undocumented.m
Normal file
31
dep/libui/darwin/undocumented.m
Normal file
@@ -0,0 +1,31 @@
|
||||
// 3 november 2017
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// functions and constants FROM THE DEPTHS BELOW!
|
||||
// note: for constants, dlsym() returns the address of the constant itself, as if we had done &constantName
|
||||
// we also provide default values just in case
|
||||
|
||||
// these values come from 10.12.6
|
||||
CFStringRef uiprivUNDOC_kCTFontPreferredSubFamilyNameKey = CFSTR("CTFontPreferredSubFamilyName");
|
||||
CFStringRef uiprivUNDOC_kCTFontPreferredFamilyNameKey = CFSTR("CTFontPreferredFamilyName");
|
||||
|
||||
// note that we treat any error as "the symbols aren't there" (and don't care if dlclose() failed)
|
||||
void uiprivLoadUndocumented(void)
|
||||
{
|
||||
void *handle;
|
||||
CFStringRef *str;
|
||||
|
||||
// dlsym() walks the dependency chain, so opening the current process should be sufficient
|
||||
handle = dlopen(NULL, RTLD_LAZY);
|
||||
if (handle == NULL)
|
||||
return;
|
||||
#define GET(var, fn) *((void **) (&var)) = dlsym(handle, #fn)
|
||||
GET(str, kCTFontPreferredSubFamilyNameKey);
|
||||
NSLog(@"get %p", str);
|
||||
if (str != NULL)
|
||||
uiprivUNDOC_kCTFontPreferredSubFamilyNameKey = *str;
|
||||
GET(str, kCTFontPreferredFamilyNameKey);
|
||||
if (str != NULL)
|
||||
uiprivUNDOC_kCTFontPreferredFamilyNameKey = *str;
|
||||
dlclose(handle);
|
||||
}
|
||||
16
dep/libui/darwin/util.m
Normal file
16
dep/libui/darwin/util.m
Normal file
@@ -0,0 +1,16 @@
|
||||
// 7 april 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// LONGTERM do we really want to do this? make it an option?
|
||||
// TODO figure out why we removed this from window.m
|
||||
void uiprivDisableAutocorrect(NSTextView *tv)
|
||||
{
|
||||
[tv setEnabledTextCheckingTypes:0];
|
||||
[tv setAutomaticDashSubstitutionEnabled:NO];
|
||||
// don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev)
|
||||
[tv setAutomaticSpellingCorrectionEnabled:NO];
|
||||
[tv setAutomaticTextReplacementEnabled:NO];
|
||||
[tv setAutomaticQuoteSubstitutionEnabled:NO];
|
||||
[tv setAutomaticLinkDetectionEnabled:NO];
|
||||
[tv setSmartInsertDeleteEnabled:NO];
|
||||
}
|
||||
407
dep/libui/darwin/window.m
Normal file
407
dep/libui/darwin/window.m
Normal file
@@ -0,0 +1,407 @@
|
||||
// 15 august 2015
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
#define defaultStyleMask (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
|
||||
|
||||
struct uiWindow {
|
||||
uiDarwinControl c;
|
||||
NSWindow *window;
|
||||
uiControl *child;
|
||||
int margined;
|
||||
int (*onClosing)(uiWindow *, void *);
|
||||
void *onClosingData;
|
||||
uiprivSingleChildConstraints constraints;
|
||||
void (*onContentSizeChanged)(uiWindow *, void *);
|
||||
void *onContentSizeChangedData;
|
||||
BOOL suppressSizeChanged;
|
||||
int fullscreen;
|
||||
int borderless;
|
||||
};
|
||||
|
||||
@implementation uiprivNSWindow
|
||||
|
||||
- (void)uiprivDoMove:(NSEvent *)initialEvent
|
||||
{
|
||||
uiprivDoManualMove(self, initialEvent);
|
||||
}
|
||||
|
||||
- (void)uiprivDoResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge
|
||||
{
|
||||
uiprivDoManualResize(self, initialEvent, edge);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface windowDelegateClass : NSObject<NSWindowDelegate> {
|
||||
uiprivMap *windows;
|
||||
}
|
||||
- (BOOL)windowShouldClose:(id)sender;
|
||||
- (void)windowDidResize:(NSNotification *)note;
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)note;
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)note;
|
||||
- (void)registerWindow:(uiWindow *)w;
|
||||
- (void)unregisterWindow:(uiWindow *)w;
|
||||
- (uiWindow *)lookupWindow:(NSWindow *)w;
|
||||
@end
|
||||
|
||||
@implementation windowDelegateClass
|
||||
|
||||
- (id)init
|
||||
{
|
||||
self = [super init];
|
||||
if (self)
|
||||
self->windows = uiprivNewMap();
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
uiprivMapDestroy(self->windows);
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (BOOL)windowShouldClose:(id)sender
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) sender)];
|
||||
// w should not be NULL; we are only the delegate of registered windows
|
||||
if ((*(w->onClosing))(w, w->onClosingData))
|
||||
uiControlDestroy(uiControl(w));
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)windowDidResize:(NSNotification *)note
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) [note object])];
|
||||
if (!w->suppressSizeChanged)
|
||||
(*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);
|
||||
}
|
||||
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)note
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) [note object])];
|
||||
if (!w->suppressSizeChanged)
|
||||
w->fullscreen = 1;
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)note
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
w = [self lookupWindow:((NSWindow *) [note object])];
|
||||
if (!w->suppressSizeChanged)
|
||||
w->fullscreen = 0;
|
||||
}
|
||||
|
||||
- (void)registerWindow:(uiWindow *)w
|
||||
{
|
||||
uiprivMapSet(self->windows, w->window, w);
|
||||
[w->window setDelegate:self];
|
||||
}
|
||||
|
||||
- (void)unregisterWindow:(uiWindow *)w
|
||||
{
|
||||
[w->window setDelegate:nil];
|
||||
uiprivMapDelete(self->windows, w->window);
|
||||
}
|
||||
|
||||
- (uiWindow *)lookupWindow:(NSWindow *)w
|
||||
{
|
||||
uiWindow *v;
|
||||
|
||||
v = uiWindow(uiprivMapGet(self->windows, w));
|
||||
// this CAN (and IS ALLOWED TO) return NULL, just in case we're called with some OS X-provided window as the key window
|
||||
return v;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static windowDelegateClass *windowDelegate = nil;
|
||||
|
||||
static void removeConstraints(uiWindow *w)
|
||||
{
|
||||
NSView *cv;
|
||||
|
||||
cv = [w->window contentView];
|
||||
uiprivSingleChildConstraintsRemove(&(w->constraints), cv);
|
||||
}
|
||||
|
||||
static void uiWindowDestroy(uiControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
// hide the window
|
||||
[w->window orderOut:w->window];
|
||||
removeConstraints(w);
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, NULL);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(w->child), nil);
|
||||
uiControlDestroy(w->child);
|
||||
}
|
||||
[windowDelegate unregisterWindow:w];
|
||||
[w->window release];
|
||||
uiFreeControl(uiControl(w));
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHandle(uiWindow, window)
|
||||
|
||||
uiControl *uiWindowParent(uiControl *c)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void uiWindowSetParent(uiControl *c, uiControl *parent)
|
||||
{
|
||||
uiUserBugCannotSetParentOnToplevel("uiWindow");
|
||||
}
|
||||
|
||||
static int uiWindowToplevel(uiControl *c)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int uiWindowVisible(uiControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
return [w->window isVisible];
|
||||
}
|
||||
|
||||
static void uiWindowShow(uiControl *c)
|
||||
{
|
||||
uiWindow *w = (uiWindow *) c;
|
||||
|
||||
[w->window makeKeyAndOrderFront:w->window];
|
||||
}
|
||||
|
||||
static void uiWindowHide(uiControl *c)
|
||||
{
|
||||
uiWindow *w = (uiWindow *) c;
|
||||
|
||||
[w->window orderOut:w->window];
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultEnabled(uiWindow, window)
|
||||
uiDarwinControlDefaultEnable(uiWindow, window)
|
||||
uiDarwinControlDefaultDisable(uiWindow, window)
|
||||
|
||||
static void uiWindowSyncEnableState(uiDarwinControl *c, int enabled)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(w), enabled))
|
||||
return;
|
||||
if (w->child != NULL)
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(w->child), enabled);
|
||||
}
|
||||
|
||||
static void uiWindowSetSuperview(uiDarwinControl *c, NSView *superview)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
static void windowRelayout(uiWindow *w)
|
||||
{
|
||||
NSView *childView;
|
||||
NSView *contentView;
|
||||
|
||||
removeConstraints(w);
|
||||
if (w->child == NULL)
|
||||
return;
|
||||
childView = (NSView *) uiControlHandle(w->child);
|
||||
contentView = [w->window contentView];
|
||||
uiprivSingleChildConstraintsEstablish(&(w->constraints),
|
||||
contentView, childView,
|
||||
uiDarwinControlHugsTrailingEdge(uiDarwinControl(w->child)),
|
||||
uiDarwinControlHugsBottom(uiDarwinControl(w->child)),
|
||||
w->margined,
|
||||
@"uiWindow");
|
||||
}
|
||||
|
||||
uiDarwinControlDefaultHugsTrailingEdge(uiWindow, window)
|
||||
uiDarwinControlDefaultHugsBottom(uiWindow, window)
|
||||
|
||||
static void uiWindowChildEdgeHuggingChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
windowRelayout(w);
|
||||
}
|
||||
|
||||
// TODO
|
||||
uiDarwinControlDefaultHuggingPriority(uiWindow, window)
|
||||
uiDarwinControlDefaultSetHuggingPriority(uiWindow, window)
|
||||
// end TODO
|
||||
|
||||
static void uiWindowChildVisibilityChanged(uiDarwinControl *c)
|
||||
{
|
||||
uiWindow *w = uiWindow(c);
|
||||
|
||||
windowRelayout(w);
|
||||
}
|
||||
|
||||
char *uiWindowTitle(uiWindow *w)
|
||||
{
|
||||
return uiDarwinNSStringToText([w->window title]);
|
||||
}
|
||||
|
||||
void uiWindowSetTitle(uiWindow *w, const char *title)
|
||||
{
|
||||
[w->window setTitle:uiprivToNSString(title)];
|
||||
}
|
||||
|
||||
void uiWindowContentSize(uiWindow *w, int *width, int *height)
|
||||
{
|
||||
NSRect r;
|
||||
|
||||
r = [w->window contentRectForFrameRect:[w->window frame]];
|
||||
*width = r.size.width;
|
||||
*height = r.size.height;
|
||||
}
|
||||
|
||||
void uiWindowSetContentSize(uiWindow *w, int width, int height)
|
||||
{
|
||||
w->suppressSizeChanged = YES;
|
||||
[w->window setContentSize:NSMakeSize(width, height)];
|
||||
w->suppressSizeChanged = NO;
|
||||
}
|
||||
|
||||
int uiWindowFullscreen(uiWindow *w)
|
||||
{
|
||||
return w->fullscreen;
|
||||
}
|
||||
|
||||
void uiWindowSetFullscreen(uiWindow *w, int fullscreen)
|
||||
{
|
||||
if (w->fullscreen && fullscreen)
|
||||
return;
|
||||
if (!w->fullscreen && !fullscreen)
|
||||
return;
|
||||
w->fullscreen = fullscreen;
|
||||
if (w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; don't toggle while borderless
|
||||
return;
|
||||
w->suppressSizeChanged = YES;
|
||||
[w->window toggleFullScreen:w->window];
|
||||
w->suppressSizeChanged = NO;
|
||||
if (!w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; restore borderless after removing
|
||||
[w->window setStyleMask:NSBorderlessWindowMask];
|
||||
}
|
||||
|
||||
void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)
|
||||
{
|
||||
w->onContentSizeChanged = f;
|
||||
w->onContentSizeChangedData = data;
|
||||
}
|
||||
|
||||
void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
|
||||
{
|
||||
w->onClosing = f;
|
||||
w->onClosingData = data;
|
||||
}
|
||||
|
||||
int uiWindowBorderless(uiWindow *w)
|
||||
{
|
||||
return w->borderless;
|
||||
}
|
||||
|
||||
void uiWindowSetBorderless(uiWindow *w, int borderless)
|
||||
{
|
||||
w->borderless = borderless;
|
||||
if (w->borderless) {
|
||||
// borderless doesn't play nice with fullscreen; wait for later
|
||||
if (!w->fullscreen)
|
||||
[w->window setStyleMask:NSBorderlessWindowMask];
|
||||
} else {
|
||||
[w->window setStyleMask:defaultStyleMask];
|
||||
// borderless doesn't play nice with fullscreen; restore state
|
||||
if (w->fullscreen) {
|
||||
w->suppressSizeChanged = YES;
|
||||
[w->window toggleFullScreen:w->window];
|
||||
w->suppressSizeChanged = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void uiWindowSetChild(uiWindow *w, uiControl *child)
|
||||
{
|
||||
NSView *childView;
|
||||
|
||||
if (w->child != NULL) {
|
||||
childView = (NSView *) uiControlHandle(w->child);
|
||||
[childView removeFromSuperview];
|
||||
uiControlSetParent(w->child, NULL);
|
||||
}
|
||||
w->child = child;
|
||||
if (w->child != NULL) {
|
||||
uiControlSetParent(w->child, uiControl(w));
|
||||
childView = (NSView *) uiControlHandle(w->child);
|
||||
uiDarwinControlSetSuperview(uiDarwinControl(w->child), [w->window contentView]);
|
||||
uiDarwinControlSyncEnableState(uiDarwinControl(w->child), uiControlEnabledToUser(uiControl(w)));
|
||||
}
|
||||
windowRelayout(w);
|
||||
}
|
||||
|
||||
int uiWindowMargined(uiWindow *w)
|
||||
{
|
||||
return w->margined;
|
||||
}
|
||||
|
||||
void uiWindowSetMargined(uiWindow *w, int margined)
|
||||
{
|
||||
w->margined = margined;
|
||||
uiprivSingleChildConstraintsSetMargined(&(w->constraints), w->margined);
|
||||
}
|
||||
|
||||
static int defaultOnClosing(uiWindow *w, void *data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
|
||||
{
|
||||
uiWindow *w;
|
||||
|
||||
uiprivFinalizeMenus();
|
||||
|
||||
uiDarwinNewControl(uiWindow, w);
|
||||
|
||||
w->window = [[uiprivNSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)
|
||||
styleMask:defaultStyleMask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:YES];
|
||||
[w->window setTitle:uiprivToNSString(title)];
|
||||
|
||||
// do NOT release when closed
|
||||
// we manually do this in uiWindowDestroy() above
|
||||
[w->window setReleasedWhenClosed:NO];
|
||||
|
||||
if (windowDelegate == nil) {
|
||||
windowDelegate = [[windowDelegateClass new] autorelease];
|
||||
[uiprivDelegates addObject:windowDelegate];
|
||||
}
|
||||
[windowDelegate registerWindow:w];
|
||||
uiWindowOnClosing(w, defaultOnClosing, NULL);
|
||||
uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
// utility function for menus
|
||||
uiWindow *uiprivWindowFromNSWindow(NSWindow *w)
|
||||
{
|
||||
if (w == nil)
|
||||
return NULL;
|
||||
if (windowDelegate == nil) // no windows were created yet; we're called with some OS X-provided window
|
||||
return NULL;
|
||||
return [windowDelegate lookupWindow:w];
|
||||
}
|
||||
253
dep/libui/darwin/winmoveresize.m
Normal file
253
dep/libui/darwin/winmoveresize.m
Normal file
@@ -0,0 +1,253 @@
|
||||
// 1 november 2016
|
||||
#import "uipriv_darwin.h"
|
||||
|
||||
// TODO option while resizing resizes both opposing sides at once (thanks swillits in irc.freenode.net/#macdev for showing this to me); figure out how far back that behavior goes when we do implement it
|
||||
|
||||
// because we are changing the window frame each time the mouse moves, the successive -[NSEvent locationInWindow]s cannot be meaningfully used together
|
||||
// make sure they are all following some sort of standard to avoid this problem; the screen is the most obvious possibility since it requires only one conversion (the only one that a NSWindow provides)
|
||||
static NSPoint makeIndependent(NSPoint p, NSWindow *w)
|
||||
{
|
||||
NSRect r;
|
||||
|
||||
r.origin = p;
|
||||
// mikeash in irc.freenode.net/#macdev confirms both that any size will do and that we can safely ignore the resultant size
|
||||
r.size = NSZeroSize;
|
||||
return [w convertRectToScreen:r].origin;
|
||||
}
|
||||
|
||||
struct onMoveDragParams {
|
||||
NSWindow *w;
|
||||
// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
|
||||
// TODO will this make things like the menubar and dock easier too?
|
||||
NSRect initialFrame;
|
||||
NSPoint initialPoint;
|
||||
};
|
||||
|
||||
void onMoveDrag(struct onMoveDragParams *p, NSEvent *e)
|
||||
{
|
||||
NSPoint new;
|
||||
NSRect frame;
|
||||
CGFloat offx, offy;
|
||||
|
||||
new = makeIndependent([e locationInWindow], p->w);
|
||||
frame = p->initialFrame;
|
||||
|
||||
offx = new.x - p->initialPoint.x;
|
||||
offy = new.y - p->initialPoint.y;
|
||||
frame.origin.x += offx;
|
||||
frame.origin.y += offy;
|
||||
|
||||
// TODO handle the menubar
|
||||
// TODO wait the system does this for us already?!
|
||||
|
||||
[p->w setFrameOrigin:frame.origin];
|
||||
}
|
||||
|
||||
void uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent)
|
||||
{
|
||||
__block struct onMoveDragParams mdp;
|
||||
uiprivNextEventArgs nea;
|
||||
BOOL (^handleEvent)(NSEvent *e);
|
||||
__block BOOL done;
|
||||
|
||||
// 10.11 gives us a method to handle this for us
|
||||
// use it if available; this lets us use the real OS dragging code, which means we can take advantage of OS features like Spaces
|
||||
if (uiprivFUTURE_NSWindow_performWindowDragWithEvent(w, initialEvent))
|
||||
return;
|
||||
|
||||
mdp.w = w;
|
||||
mdp.initialFrame = [mdp.w frame];
|
||||
mdp.initialPoint = makeIndependent([initialEvent locationInWindow], mdp.w);
|
||||
|
||||
nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
|
||||
nea.duration = [NSDate distantFuture];
|
||||
nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking
|
||||
nea.dequeue = YES;
|
||||
handleEvent = ^(NSEvent *e) {
|
||||
if ([e type] == NSLeftMouseUp) {
|
||||
done = YES;
|
||||
return YES; // do not send
|
||||
}
|
||||
onMoveDrag(&mdp, e);
|
||||
return YES; // do not send
|
||||
};
|
||||
done = NO;
|
||||
while (uiprivMainStep(&nea, handleEvent))
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
|
||||
// see http://stackoverflow.com/a/40352996/3408572
|
||||
static void minMaxAutoLayoutSizes(NSWindow *w, NSSize *min, NSSize *max)
|
||||
{
|
||||
NSLayoutConstraint *cw, *ch;
|
||||
NSView *contentView;
|
||||
NSRect prevFrame;
|
||||
|
||||
// if adding these constraints causes the window to change size somehow, don't show it to the user and change it back afterwards
|
||||
NSDisableScreenUpdates();
|
||||
prevFrame = [w frame];
|
||||
|
||||
// minimum: encourage the window to be as small as possible
|
||||
contentView = [w contentView];
|
||||
cw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, 0,
|
||||
@"window minimum width finding constraint");
|
||||
[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:cw];
|
||||
ch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, 0,
|
||||
@"window minimum height finding constraint");
|
||||
[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:ch];
|
||||
*min = [contentView fittingSize];
|
||||
[contentView removeConstraint:cw];
|
||||
[contentView removeConstraint:ch];
|
||||
|
||||
// maximum: encourage the window to be as large as possible
|
||||
contentView = [w contentView];
|
||||
cw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, CGFLOAT_MAX,
|
||||
@"window maximum width finding constraint");
|
||||
[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:cw];
|
||||
ch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight,
|
||||
NSLayoutRelationEqual,
|
||||
nil, NSLayoutAttributeNotAnAttribute,
|
||||
0, CGFLOAT_MAX,
|
||||
@"window maximum height finding constraint");
|
||||
[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
|
||||
[contentView addConstraint:ch];
|
||||
*max = [contentView fittingSize];
|
||||
[contentView removeConstraint:cw];
|
||||
[contentView removeConstraint:ch];
|
||||
|
||||
[w setFrame:prevFrame display:YES]; // TODO really YES?
|
||||
NSEnableScreenUpdates();
|
||||
}
|
||||
|
||||
static void handleResizeLeft(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->origin.x += new.x - old.x;
|
||||
frame->size.width -= new.x - old.x;
|
||||
}
|
||||
|
||||
// TODO properly handle the menubar
|
||||
// TODO wait, OS X does it for us?!
|
||||
static void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->size.height += new.y - old.y;
|
||||
}
|
||||
|
||||
static void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->size.width += new.x - old.x;
|
||||
}
|
||||
|
||||
|
||||
// TODO properly handle the menubar
|
||||
static void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new)
|
||||
{
|
||||
frame->origin.y += new.y - old.y;
|
||||
frame->size.height -= new.y - old.y;
|
||||
}
|
||||
|
||||
struct onResizeDragParams {
|
||||
NSWindow *w;
|
||||
// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
|
||||
// TODO will this make things like the menubar and dock easier too?
|
||||
NSRect initialFrame;
|
||||
NSPoint initialPoint;
|
||||
uiWindowResizeEdge edge;
|
||||
NSSize min;
|
||||
NSSize max;
|
||||
};
|
||||
|
||||
static void onResizeDrag(struct onResizeDragParams *p, NSEvent *e)
|
||||
{
|
||||
NSPoint new;
|
||||
NSRect frame;
|
||||
|
||||
new = makeIndependent([e locationInWindow], p->w);
|
||||
frame = p->initialFrame;
|
||||
|
||||
// horizontal
|
||||
switch (p->edge) {
|
||||
case uiWindowResizeEdgeLeft:
|
||||
case uiWindowResizeEdgeTopLeft:
|
||||
case uiWindowResizeEdgeBottomLeft:
|
||||
handleResizeLeft(&frame, p->initialPoint, new);
|
||||
break;
|
||||
case uiWindowResizeEdgeRight:
|
||||
case uiWindowResizeEdgeTopRight:
|
||||
case uiWindowResizeEdgeBottomRight:
|
||||
handleResizeRight(&frame, p->initialPoint, new);
|
||||
break;
|
||||
}
|
||||
// vertical
|
||||
switch (p->edge) {
|
||||
case uiWindowResizeEdgeTop:
|
||||
case uiWindowResizeEdgeTopLeft:
|
||||
case uiWindowResizeEdgeTopRight:
|
||||
handleResizeTop(&frame, p->initialPoint, new);
|
||||
break;
|
||||
case uiWindowResizeEdgeBottom:
|
||||
case uiWindowResizeEdgeBottomLeft:
|
||||
case uiWindowResizeEdgeBottomRight:
|
||||
handleResizeBottom(&frame, p->initialPoint, new);
|
||||
break;
|
||||
}
|
||||
|
||||
// constrain
|
||||
// TODO should we constrain against anything else as well? minMaxAutoLayoutSizes() already gives us nonnegative sizes, but...
|
||||
if (frame.size.width < p->min.width)
|
||||
frame.size.width = p->min.width;
|
||||
if (frame.size.height < p->min.height)
|
||||
frame.size.height = p->min.height;
|
||||
// TODO > or >= ?
|
||||
if (frame.size.width > p->max.width)
|
||||
frame.size.width = p->max.width;
|
||||
if (frame.size.height > p->max.height)
|
||||
frame.size.height = p->max.height;
|
||||
|
||||
[p->w setFrame:frame display:YES]; // and do reflect the new frame immediately
|
||||
}
|
||||
|
||||
// TODO do our events get fired with this? *should* they?
|
||||
void uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge)
|
||||
{
|
||||
__block struct onResizeDragParams rdp;
|
||||
uiprivNextEventArgs nea;
|
||||
BOOL (^handleEvent)(NSEvent *e);
|
||||
__block BOOL done;
|
||||
|
||||
rdp.w = w;
|
||||
rdp.initialFrame = [rdp.w frame];
|
||||
rdp.initialPoint = makeIndependent([initialEvent locationInWindow], rdp.w);
|
||||
rdp.edge = edge;
|
||||
// TODO what happens if these change during the loop?
|
||||
minMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max));
|
||||
|
||||
nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
|
||||
nea.duration = [NSDate distantFuture];
|
||||
nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking
|
||||
nea.dequeue = YES;
|
||||
handleEvent = ^(NSEvent *e) {
|
||||
if ([e type] == NSLeftMouseUp) {
|
||||
done = YES;
|
||||
return YES; // do not send
|
||||
}
|
||||
onResizeDrag(&rdp, e);
|
||||
return YES; // do not send
|
||||
};
|
||||
done = NO;
|
||||
while (uiprivMainStep(&nea, handleEvent))
|
||||
if (done)
|
||||
break;
|
||||
}
|
||||
BIN
dep/libui/examples/controlgallery/darwin.png
Normal file
BIN
dep/libui/examples/controlgallery/darwin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user