diff options
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash')
55 files changed, 0 insertions, 9458 deletions
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/.gitignore b/examples/go-dashboard/src/github.com/mum4k/termdash/.gitignore deleted file mode 100644 index 97e9bcbaa..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Exclude MacOS attribute files. -.DS_Store diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/.travis.yml b/examples/go-dashboard/src/github.com/mum4k/termdash/.travis.yml deleted file mode 100644 index 7c8b739a0..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: go -go: - - 1.14.x - - 1.15.x - - stable -script: - - go get -t ./... - - go get -u golang.org/x/lint/golint - - go test ./... - - CGO_ENABLED=1 go test -race ./... - - go vet ./... - - diff -u <(echo -n) <(gofmt -d -s .) - - diff -u <(echo -n) <(./internal/scripts/autogen_licences.sh .) - - diff -u <(echo -n) <(golint ./...) -env: - global: - - CGO_ENABLED=0 diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/CHANGELOG.md b/examples/go-dashboard/src/github.com/mum4k/termdash/CHANGELOG.md deleted file mode 100644 index 6889100b4..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/CHANGELOG.md +++ /dev/null @@ -1,361 +0,0 @@ -# Changelog - -All notable changes to this project are documented here. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.12.2] - 31-Aug-2020 - -### Fixed - -- advanced the CI Go versions up to Go 1.15. -- fixed the build status badge to correctly point to travis-ci.com instead of - travis-ci.org. - -## [0.12.1] - 20-Jun-2020 - -### Fixed - -- the `tcell` unit test can now pass in headless mode (when TERM="") which - happens under bazel. -- switching coveralls integration to Github application. - -## [0.12.0] - 10-Apr-2020 - -### Added - -- Migrating to [Go modules](https://blog.golang.org/using-go-modules). -- Renamed directory `internal` to `private` so that external widget development - is possible. Noted in - [README.md](https://github.com/mum4k/termdash/blob/master/README.md) that packages in the - `private` directory don't have any API stability guarantee. - -## [0.11.0] - 7-Mar-2020 - -#### Breaking API changes - -- Termdash now requires at least Go version 1.11. - -### Added - -- New [`tcell`](https://github.com/gdamore/tcell) based terminal implementation - which implements the `terminalapi.Terminal` interface. -- tcell implementation supports two initialization `Option`s: - - `ColorMode` the terminal color output mode (defaults to 256 color mode) - - `ClearStyle` the foreground and background color style to use when clearing - the screen (defaults to the global ColorDefault for both foreground and - background) - -### Fixed - -- Improved test coverage of the `Gauge` widget. - -## [0.10.0] - 5-Jun-2019 - -### Added - -- Added `time.Duration` based `ValueFormatter` for the `LineChart` Y-axis labels. -- Added round and suffix `ValueFormatter` for the `LineChart` Y-axis labels. -- Added decimal and suffix `ValueFormatter` for the `LineChart` Y-axis labels. -- Added a `container.SplitOption` that allows fixed size container splits. -- Added `grid` functions that allow fixed size rows and columns. - -### Changed - -- The `LineChart` can format the labels on the Y-axis with a `ValueFormatter`. -- The `SegmentDisplay` can now display dots and colons ('.' and ':'). -- The `Donut` widget now guarantees spacing between the donut and its label. -- The continuous build on Travis CI now builds with cgo explicitly disabled to - ensure both Termdash and its dependencies use pure Go. - -### Fixed - -- Lint issues found on the Go report card. -- An internal library belonging to the `Text` widget was incorrectly passing - `math.MaxUint32` as an int argument. - -## [0.9.1] - 15-May-2019 - -### Fixed - -- Termdash could deadlock when a `Button` or a `TextInput` was configured to - call the `Container.Update` method. - -## [0.9.0] - 28-Apr-2019 - -### Added - -- The `TextInput` widget, an input field allowing interactive text input. -- The `Donut` widget can now display an optional text label under the donut. - -### Changed - -- Widgets now get information whether their container is focused when Draw is - executed. -- The SegmentDisplay widget now has a method that returns the observed character - capacity the last time Draw was called. -- The grid.Builder API now allows users to specify options for intermediate - containers, i.e. containers that don't have widgets, but represent rows and - columns. -- Line chart widget now allows `math.NaN` values to represent "no value" (values - that will not be rendered) in the values slice. - -#### Breaking API changes - -- The widgetapi.Widget.Draw method now accepts a second argument which provides - widgets with additional metadata. This affects all implemented widgets. -- Termdash now requires at least Go version 1.10, which allows us to utilize - `math.Round` instead of our own implementation and `strings.Builder` instead - of `bytes.Buffer`. -- Terminal shortcuts like `Ctrl-A` no longer come as two separate events, - Termdash now mirrors termbox-go and sends these as one event. - -## [0.8.0] - 30-Mar-2019 - -### Added - -- New API for building layouts, a grid.Builder. Allows defining the layout - iteratively as repetitive Elements, Rows and Columns. -- Containers now support margin around them and padding of their content. -- Container now supports dynamic layout changes via the new Update method. - -### Changed - -- The Text widget now supports content wrapping on word boundaries. -- The BarChart and SparkLine widgets now have a method that returns the - observed value capacity the last time Draw was called. -- Moving widgetapi out of the internal directory to allow external users to - develop their own widgets. -- Event delivery to widgets now has a stable defined order and happens when the - container is unlocked so that widgets can trigger dynamic layout changes. - -### Fixed - -- The termdash_test now correctly waits until all subscribers processed events, - not just received them. -- Container focus tracker now correctly tracks focus changes in enlarged areas, - i.e. when the terminal size increased. -- The BarChart, LineChart and SegmentDisplay widgets now protect against - external mutation of the values passed into them by copying the data they - receive. - -## [0.7.2] - 25-Feb-2019 - -### Added - -- Test coverage for data only packages. - -### Changed - -- Refactoring packages that contained a mix of public and internal identifiers. - -#### Breaking API changes - -The following packages were refactored, no impact is expected as the removed -identifiers shouldn't be used externally. - -- Functions align.Text and align.Rectangle were moved to a new - internal/alignfor package. -- Types cell.Cell and cell.Buffer were moved into a new internal/canvas/buffer - package. - -## [0.7.1] - 24-Feb-2019 - -### Fixed - -- Some of the packages that were moved into internal are required externally. - This release makes them available again. - -### Changed - -#### Breaking API changes - -- The draw.LineStyle enum was refactored into its own package - linestyle.LineStyle. Users will have to replace: - - - draw.LineStyleNone -> linestyle.None - - draw.LineStyleLight -> linestyle.Light - - draw.LineStyleDouble -> linestyle.Double - - draw.LineStyleRound -> linestyle.Round - -## [0.7.0] - 24-Feb-2019 - -### Added - -#### New widgets - -- The Button widget. - -#### Improvements to documentation - -- Clearly marked the public API surface by moving private packages into - internal directory. -- Started a GitHub wiki for Termdash. - -#### Improvements to the LineChart widget - -- The LineChart widget can display X axis labels in vertical orientation. -- The LineChart widget allows the user to specify a custom scale for the Y - axis. -- The LineChart widget now has an option that disables scaling of the X axis. - Useful for applications that want to continuously feed data and make them - "roll" through the linechart. -- The LineChart widget now has a method that returns the observed capacity of - the LineChart the last time Draw was called. -- The LineChart widget now supports zoom of the content triggered by mouse - events. - -#### Improvements to the Text widget - -- The Text widget now has a Write option that atomically replaces the entire - text content. - -#### Improvements to the infrastructure - -- A function that draws text vertically. -- A non-blocking event distribution system that can throttle repetitive events. -- Generalized mouse button FSM for use in widgets that need to track mouse - button clicks. - -### Changed - -- Termbox is now initialized in 256 color mode by default. -- The infrastructure now uses the non-blocking event distribution system to - distribute events to subscribers. Each widget is now an individual - subscriber. -- The infrastructure now throttles event driven screen redraw rather than - redrawing for each input event. -- Widgets can now specify the scope at which they want to receive keyboard and - mouse events. - -#### Breaking API changes - -##### High impact - -- The constructors of all the widgets now also return an error so that they - can validate the options. This is a breaking change for the following - widgets: BarChart, Gauge, LineChart, SparkLine, Text. The callers will have - to handle the returned error. - -##### Low impact - -- The container package no longer exports separate methods to receive Keyboard - and Mouse events which were replaced by a Subscribe method for the event - distribution system. This shouldn't affect users as the removed methods - aren't needed by container users. -- The widgetapi.Options struct now uses an enum instead of a boolean when - widget specifies if it wants keyboard or mouse events. This only impacts - development of new widgets. - -### Fixed - -- The LineChart widget now correctly determines the Y axis scale when multiple - series are provided. -- Lint issues in the codebase, and updated Travis configuration so that golint - is executed on every run. -- Termdash now correctly starts in locales like zh_CN.UTF-8 where some of the - characters it uses internally can have ambiguous width. - -## [0.6.1] - 12-Feb-2019 - -### Fixed - -- The LineChart widget now correctly places custom labels. - -## [0.6.0] - 07-Feb-2019 - -### Added - -- The SegmentDisplay widget. -- A CHANGELOG. -- New line styles for borders. - -### Changed - -- Better recordings of the individual demos. - -### Fixed - -- The LineChart now has an option to change the behavior of the Y axis from - zero anchored to adaptive. -- Lint errors reported on the Go report card. -- Widgets now correctly handle a race when new user data are supplied between - calls to their Options() and Draw() methods. - -## [0.5.0] - 21-Jan-2019 - -### Added - -- Draw primitives for drawing circles. -- The Donut widget. - -### Fixed - -- Bugfixes in the braille canvas. -- Lint errors reported on the Go report card. -- Flaky behavior in termdash_test. - -## [0.4.0] - 15-Jan-2019 - -### Added - -- 256 color support. -- Variable size container splits. -- A more complete demo of the functionality. - -### Changed - -- Updated documentation and README. - -## [0.3.0] - 13-Jan-2019 - -### Added - -- Primitives for drawing lines. -- Implementation of a Braille canvas. -- The LineChart widget. - -## [0.2.0] - 02-Jul-2018 - -### Added - -- The SparkLine widget. -- The BarChart widget. -- Manually triggered redraw. -- Travis now checks for presence of licence headers. - -### Fixed - -- Fixing races in termdash_test. - -## 0.1.0 - 13-Jun-2018 - -### Added - -- Documentation of the project and its goals. -- Drawing infrastructure. -- Testing infrastructure. -- The Gauge widget. -- The Text widget. - -[unreleased]: https://github.com/mum4k/termdash/compare/v0.12.2...devel -[0.12.2]: https://github.com/mum4k/termdash/compare/v0.12.1...v0.12.2 -[0.12.1]: https://github.com/mum4k/termdash/compare/v0.12.0...v0.12.1 -[0.12.0]: https://github.com/mum4k/termdash/compare/v0.11.0...v0.12.0 -[0.11.0]: https://github.com/mum4k/termdash/compare/v0.10.0...v0.11.0 -[0.10.0]: https://github.com/mum4k/termdash/compare/v0.9.1...v0.10.0 -[0.9.1]: https://github.com/mum4k/termdash/compare/v0.9.0...v0.9.1 -[0.9.0]: https://github.com/mum4k/termdash/compare/v0.8.0...v0.9.0 -[0.8.0]: https://github.com/mum4k/termdash/compare/v0.7.2...v0.8.0 -[0.7.2]: https://github.com/mum4k/termdash/compare/v0.7.1...v0.7.2 -[0.7.1]: https://github.com/mum4k/termdash/compare/v0.7.0...v0.7.1 -[0.7.0]: https://github.com/mum4k/termdash/compare/v0.6.1...v0.7.0 -[0.6.1]: https://github.com/mum4k/termdash/compare/v0.6.0...v0.6.1 -[0.6.0]: https://github.com/mum4k/termdash/compare/v0.5.0...v0.6.0 -[0.5.0]: https://github.com/mum4k/termdash/compare/v0.4.0...v0.5.0 -[0.4.0]: https://github.com/mum4k/termdash/compare/v0.3.0...v0.4.0 -[0.3.0]: https://github.com/mum4k/termdash/compare/v0.2.0...v0.3.0 -[0.2.0]: https://github.com/mum4k/termdash/compare/v0.1.0...v0.2.0 diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/CONTRIBUTING.md b/examples/go-dashboard/src/github.com/mum4k/termdash/CONTRIBUTING.md deleted file mode 100644 index 9f2027288..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/CONTRIBUTING.md +++ /dev/null @@ -1,38 +0,0 @@ -# How to Contribute - -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. - -## Fork and merge into the "devel" branch - -All development in termdash repository must happen in the [devel -branch](https://github.com/mum4k/termdash/tree/devel). The devel branch is -merged into the master branch during release of each new version. - -When you fork the termdash repository, be sure to checkout the devel branch. -When you are creating a pull request, be sure to pull back into the devel -branch. - -## Contributor License Agreement - -Contributions to this project must be accompanied by a Contributor License -Agreement. You (or your employer) retain the copyright to your contribution; -this simply gives us permission to use and redistribute your contributions as -part of the project. Head over to <https://cla.developers.google.com/> to see -your current agreements on file or to sign a new one. - -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. - -## Code reviews - -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. - -## Community Guidelines - -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/LICENSE b/examples/go-dashboard/src/github.com/mum4k/termdash/LICENSE deleted file mode 100644 index 261eeb9e9..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/README.md b/examples/go-dashboard/src/github.com/mum4k/termdash/README.md deleted file mode 100644 index 8ecd1fd53..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/README.md +++ /dev/null @@ -1,215 +0,0 @@ -[](https://godoc.org/github.com/mum4k/termdash) -[](https://travis-ci.com/mum4k/termdash) -[](https://sourcegraph.com/github.com/mum4k/termdash?badge) -[](https://coveralls.io/github/mum4k/termdash?branch=master) -[](https://goreportcard.com/report/github.com/mum4k/termdash) -[](https://github.com/mum4k/termdash/blob/master/LICENSE) -[](https://github.com/avelino/awesome-go) - -# [<img src="./doc/images/termdash.png" alt="termdashlogo" type="image/png" width="30%">](http://github.com/mum4k/termdash/wiki) - -Termdash is a cross-platform customizable terminal based dashboard. - -[<img src="./doc/images/termdashdemo_0_9_0.gif" alt="termdashdemo" type="image/gif">](termdashdemo/termdashdemo.go) - -The feature set is inspired by the -[gizak/termui](http://github.com/gizak/termui) project, which in turn was -inspired by -[yaronn/blessed-contrib](http://github.com/yaronn/blessed-contrib). - -This rewrite focuses on code readability, maintainability and testability, see -the [design goals](doc/design_goals.md). It aims to achieve the following -[requirements](doc/requirements.md). See the [high-level design](doc/hld.md) -for more details. - -# Public API and status - -The public API surface is documented in the -[wiki](http://github.com/mum4k/termdash/wiki). - -Private packages can be identified by the presence of the **/private/** -directory in their import path. Stability of the private packages isn't -guaranteed and changes won't be backward compatible. - -There might still be breaking changes to the public API, at least until the -project reaches version 1.0.0. Any breaking changes will be published in the -[changelog](CHANGELOG.md). - -# Current feature set - -- Full support for terminal window resizing throughout the infrastructure. -- Customizable layout, widget placement, borders, margins, padding, colors, etc. -- Dynamic layout changes at runtime. -- Binary tree and Grid forms of setting up the layout. -- Focusable containers and widgets. -- Processing of keyboard and mouse events. -- Periodic and event driven screen redraw. -- A library of widgets, see below. -- UTF-8 for all text elements. -- Drawing primitives (Go functions) for widget development with character and - sub-character resolution. - -# Installation - -To install this library, run the following: - -```go -go get -u github.com/mum4k/termdash -``` - -# Usage - -The usage of most of these elements is demonstrated in -[termdashdemo.go](termdashdemo/termdashdemo.go). To execute the demo: - -```go -go run github.com/mum4k/termdash/termdashdemo/termdashdemo.go -``` - -# Documentation - -Please refer to the [Termdash wiki](http://github.com/mum4k/termdash/wiki) for -all documentation and resources. - -# Implemented Widgets - -## The Button - -Allows users to interact with the application, each button press runs a callback function. -Run the -[buttondemo](widgets/button/buttondemo/buttondemo.go). - -```go -go run github.com/mum4k/termdash/widgets/button/buttondemo/buttondemo.go -``` - -[<img src="./doc/images/buttondemo.gif" alt="buttondemo" type="image/gif" width="50%">](widgets/button/buttondemo/buttondemo.go) - -## The TextInput - -Allows users to interact with the application by entering, editing and -submitting text data. Run the -[textinputdemo](widgets/textinput/textinputdemo/textinputdemo.go). - -```go -go run github.com/mum4k/termdash/widgets/textinput/textinputdemo/textinputdemo.go -``` - -[<img src="./doc/images/textinputdemo.gif" alt="textinputdemo" type="image/gif" width="80%">](widgets/textinput/textinputdemo/textinputdemo.go) - -## The Gauge - -Displays the progress of an operation. Run the -[gaugedemo](widgets/gauge/gaugedemo/gaugedemo.go). - -```go -go run github.com/mum4k/termdash/widgets/gauge/gaugedemo/gaugedemo.go -``` - -[<img src="./doc/images/gaugedemo.gif" alt="gaugedemo" type="image/gif">](widgets/gauge/gaugedemo/gaugedemo.go) - -## The Donut - -Visualizes progress of an operation as a partial or a complete donut. Run the -[donutdemo](widgets/donut/donutdemo/donutdemo.go). - -```go -go run github.com/mum4k/termdash/widgets/donut/donutdemo/donutdemo.go -``` - -[<img src="./doc/images/donutdemo.gif" alt="donutdemo" type="image/gif">](widgets/donut/donutdemo/donutdemo.go) - -## The Text - -Displays text content, supports trimming and scrolling of content. Run the -[textdemo](widgets/text/textdemo/textdemo.go). - -```go -go run github.com/mum4k/termdash/widgets/text/textdemo/textdemo.go -``` - -[<img src="./doc/images/textdemo.gif" alt="textdemo" type="image/gif">](widgets/text/textdemo/textdemo.go) - -## The SparkLine - -Draws a graph showing a series of values as vertical bars. The bars can have -sub-cell height. Run the -[sparklinedemo](widgets/sparkline/sparklinedemo/sparklinedemo.go). - -```go -go run github.com/mum4k/termdash/widgets/sparkline/sparklinedemo/sparklinedemo.go -``` - -[<img src="./doc/images/sparklinedemo.gif" alt="sparklinedemo" type="image/gif" width="50%">](widgets/sparkline/sparklinedemo/sparklinedemo.go) - -## The BarChart - -Displays multiple bars showing relative ratios of values. Run the -[barchartdemo](widgets/barchart/barchartdemo/barchartdemo.go). - -```go -go run github.com/mum4k/termdash/widgets/barchart/barchartdemo/barchartdemo.go -``` - -[<img src="./doc/images/barchartdemo.gif" alt="barchartdemo" type="image/gif" width="50%">](widgets/barchart/barchartdemo/barchartdemo.go) - -## The LineChart - -Displays series of values on a line chart, supports zoom triggered by mouse -events. Run the -[linechartdemo](widgets/linechart/linechartdemo/linechartdemo.go). - -```go -go run github.com/mum4k/termdash/widgets/linechart/linechartdemo/linechartdemo.go -``` - -[<img src="./doc/images/linechartdemo.gif" alt="linechartdemo" type="image/gif" width="70%">](widgets/linechart/linechartdemo/linechartdemo.go) - -## The SegmentDisplay - -Displays text by simulating a 16-segment display. Run the -[segmentdisplaydemo](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go). - -```go -go run github.com/mum4k/termdash/widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go -``` - -[<img src="./doc/images/segmentdisplaydemo.gif" alt="segmentdisplaydemo" type="image/gif">](widgets/segmentdisplay/segmentdisplaydemo/segmentdisplaydemo.go) - -# Contributing - -If you are willing to contribute, improve the infrastructure or develop a -widget, first of all Thank You! Your help is appreciated. - -Please see the [CONTRIBUTING.md](CONTRIBUTING.md) file for guidelines related -to the Google's CLA, and code review requirements. - -As stated above the primary goal of this project is to develop readable, well -designed code, the functionality and efficiency come second. This is achieved -through detailed code reviews, design discussions and following of the [design -guidelines](doc/design_guidelines.md). Please familiarize yourself with these -before contributing. - -If you're developing a new widget, please see the [widget -development](doc/widget_development.md) section. - -Termdash uses [this branching model](https://nvie.com/posts/a-successful-git-branching-model/). When you fork the repository, base your changes off the [devel](https://github.com/mum4k/termdash/tree/devel) branch and the pull request should merge it back to the devel branch. Commits to the master branch are limited to releases, major bug fixes and documentation updates. - -# Similar projects in Go - -- [clui](https://github.com/VladimirMarkelov/clui) -- [gocui](https://github.com/jroimartin/gocui) -- [gowid](https://github.com/gcla/gowid) -- [termui](https://github.com/gizak/termui) -- [tui-go](https://github.com/marcusolsson/tui-go) -- [tview](https://github.com/rivo/tview) - -# Projects using Termdash - -- [datadash](https://github.com/keithknott26/datadash): Visualize streaming or tabular data inside the terminal. -- [grafterm](https://github.com/slok/grafterm): Metrics dashboards visualization on the terminal. -- [perfstat](https://github.com/flaviostutz/perfstat): Analyze and show tips about possible bottlenecks in Linux systems. - -# Disclaimer - -This is not an official Google product. diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/align/align.go b/examples/go-dashboard/src/github.com/mum4k/termdash/align/align.go deleted file mode 100644 index da4087f57..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/align/align.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package align defines constants representing types of alignment. -package align - -// Horizontal indicates the type of horizontal alignment. -type Horizontal int - -// String implements fmt.Stringer() -func (h Horizontal) String() string { - if n, ok := horizontalNames[h]; ok { - return n - } - return "HorizontalUnknown" -} - -// horizontalNames maps Horizontal values to human readable names. -var horizontalNames = map[Horizontal]string{ - HorizontalLeft: "HorizontalLeft", - HorizontalCenter: "HorizontalCenter", - HorizontalRight: "HorizontalRight", -} - -const ( - // HorizontalLeft is left alignment along the horizontal axis. - HorizontalLeft Horizontal = iota - // HorizontalCenter is center alignment along the horizontal axis. - HorizontalCenter - // HorizontalRight is right alignment along the horizontal axis. - HorizontalRight -) - -// Vertical indicates the type of vertical alignment. -type Vertical int - -// String implements fmt.Stringer() -func (v Vertical) String() string { - if n, ok := verticalNames[v]; ok { - return n - } - return "VerticalUnknown" -} - -// verticalNames maps Vertical values to human readable names. -var verticalNames = map[Vertical]string{ - VerticalTop: "VerticalTop", - VerticalMiddle: "VerticalMiddle", - VerticalBottom: "VerticalBottom", -} - -const ( - // VerticalTop is top alignment along the vertical axis. - VerticalTop Vertical = iota - // VerticalMiddle is middle alignment along the vertical axis. - VerticalMiddle - // VerticalBottom is bottom alignment along the vertical axis. - VerticalBottom -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/cell/cell.go b/examples/go-dashboard/src/github.com/mum4k/termdash/cell/cell.go deleted file mode 100644 index c3eb6df24..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/cell/cell.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package cell implements cell options and attributes. -package cell - -// Option is used to provide options for cells on a 2-D terminal. -type Option interface { - // Set sets the provided option. - Set(*Options) -} - -// Options stores the provided options. -type Options struct { - FgColor Color - BgColor Color -} - -// Set allows existing options to be passed as an option. -func (o *Options) Set(other *Options) { - *other = *o -} - -// NewOptions returns a new Options instance after applying the provided options. -func NewOptions(opts ...Option) *Options { - o := &Options{} - for _, opt := range opts { - opt.Set(o) - } - return o -} - -// option implements Option. -type option func(*Options) - -// Set implements Option.set. -func (co option) Set(opts *Options) { - co(opts) -} - -// FgColor sets the foreground color of the cell. -func FgColor(color Color) Option { - return option(func(co *Options) { - co.FgColor = color - }) -} - -// BgColor sets the background color of the cell. -func BgColor(color Color) Option { - return option(func(co *Options) { - co.BgColor = color - }) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/cell/color.go b/examples/go-dashboard/src/github.com/mum4k/termdash/cell/color.go deleted file mode 100644 index 94560b74c..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/cell/color.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cell - -import ( - "fmt" -) - -// color.go defines constants for cell colors. - -// Color is the color of a cell. -type Color int - -// String implements fmt.Stringer() -func (cc Color) String() string { - if n, ok := colorNames[cc]; ok { - return n - } - return fmt.Sprintf("Color:%d", cc) -} - -// colorNames maps Color values to human readable names. -var colorNames = map[Color]string{ - ColorDefault: "ColorDefault", - ColorBlack: "ColorBlack", - ColorRed: "ColorRed", - ColorGreen: "ColorGreen", - ColorYellow: "ColorYellow", - ColorBlue: "ColorBlue", - ColorMagenta: "ColorMagenta", - ColorCyan: "ColorCyan", - ColorWhite: "ColorWhite", -} - -// The supported terminal colors. -const ( - ColorDefault Color = iota - - // 8 "system" colors. - ColorBlack - ColorRed - ColorGreen - ColorYellow - ColorBlue - ColorMagenta - ColorCyan - ColorWhite -) - -// ColorNumber sets a color using its number. -// Make sure your terminal is set to a terminalapi.ColorMode that supports the -// target color. The provided value must be in the range 0-255. -// Larger or smaller values will be reset to the default color. -// -// For reference on these colors see the Xterm number in: -// https://jonasjacek.github.io/colors/ -func ColorNumber(n int) Color { - if n < 0 || n > 255 { - return ColorDefault - } - return Color(n + 1) // Colors are off-by-one due to ColorDefault being zero. -} - -// ColorRGB6 sets a color using the 6x6x6 terminal color. -// Make sure your terminal is set to the terminalapi.ColorMode256 mode. -// The provided values (r, g, b) must be in the range 0-5. -// Larger or smaller values will be reset to the default color. -// -// For reference on these colors see: -// https://superuser.com/questions/783656/whats-the-deal-with-terminal-colors -func ColorRGB6(r, g, b int) Color { - for _, c := range []int{r, g, b} { - if c < 0 || c > 5 { - return ColorDefault - } - } - return Color(0x10 + 36*r + 6*g + b + 1) // Colors are off-by-one due to ColorDefault being zero. -} - -// ColorRGB24 sets a color using the 24 bit web color scheme. -// Make sure your terminal is set to the terminalapi.ColorMode256 mode. -// The provided values (r, g, b) must be in the range 0-255. -// Larger or smaller values will be reset to the default color. -// -// For reference on these colors see the RGB column in: -// https://jonasjacek.github.io/colors/ -func ColorRGB24(r, g, b int) Color { - for _, c := range []int{r, g, b} { - if c < 0 || c > 255 { - return ColorDefault - } - } - return ColorRGB6(r/51, g/51, b/51) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go deleted file mode 100644 index 54cef7856..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/container.go +++ /dev/null @@ -1,471 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Package container defines a type that wraps other containers or widgets. - -The container supports splitting container into sub containers, defining -container styles and placing widgets. The container also creates and manages -canvases assigned to the placed widgets. -*/ -package container - -import ( - "errors" - "fmt" - "image" - "sync" - - "github.com/mum4k/termdash/linestyle" - "github.com/mum4k/termdash/private/alignfor" - "github.com/mum4k/termdash/private/area" - "github.com/mum4k/termdash/private/event" - "github.com/mum4k/termdash/terminal/terminalapi" - "github.com/mum4k/termdash/widgetapi" -) - -// Container wraps either sub containers or widgets and positions them on the -// terminal. -// This is thread-safe. -type Container struct { - // parent is the parent container, nil if this is the root container. - parent *Container - // The sub containers, if these aren't nil, the widget must be. - first *Container - second *Container - - // term is the terminal this container is placed on. - // All containers in the tree share the same terminal. - term terminalapi.Terminal - - // focusTracker tracks the active (focused) container. - // All containers in the tree share the same tracker. - focusTracker *focusTracker - - // area is the area of the terminal this container has access to. - // Initialized the first time Draw is called. - area image.Rectangle - - // opts are the options provided to the container. - opts *options - - // clearNeeded indicates if the terminal needs to be cleared next time we - // are clearNeeded the container. - // This is required if the container was updated and thus the layout might - // have changed. - clearNeeded bool - - // mu protects the container tree. - // All containers in the tree share the same lock. - mu *sync.Mutex -} - -// String represents the container metadata in a human readable format. -// Implements fmt.Stringer. -func (c *Container) String() string { - return fmt.Sprintf("Container@%p{parent:%p, first:%p, second:%p, area:%+v}", c, c.parent, c.first, c.second, c.area) -} - -// New returns a new root container that will use the provided terminal and -// applies the provided options. -func New(t terminalapi.Terminal, opts ...Option) (*Container, error) { - root := &Container{ - term: t, - opts: newOptions( /* parent = */ nil), - mu: &sync.Mutex{}, - } - - // Initially the root is focused. - root.focusTracker = newFocusTracker(root) - if err := applyOptions(root, opts...); err != nil { - return nil, err - } - if err := validateOptions(root); err != nil { - return nil, err - } - return root, nil -} - -// newChild creates a new child container of the given parent. -func newChild(parent *Container, opts []Option) (*Container, error) { - child := &Container{ - parent: parent, - term: parent.term, - focusTracker: parent.focusTracker, - opts: newOptions(parent.opts), - mu: parent.mu, - } - if err := applyOptions(child, opts...); err != nil { - return nil, err - } - return child, nil -} - -// hasBorder determines if this container has a border. -func (c *Container) hasBorder() bool { - return c.opts.border != linestyle.None -} - -// hasWidget determines if this container has a widget. -func (c *Container) hasWidget() bool { - return c.opts.widget != nil -} - -// usable returns the usable area in this container. -// This depends on whether the container has a border, etc. -func (c *Container) usable() image.Rectangle { - if c.hasBorder() { - return area.ExcludeBorder(c.area) - } - return c.area -} - -// widgetArea returns the area in the container that is available for the -// widget's canvas. Takes the container border, widget's requested maximum size -// and ratio and container's alignment into account. -// Returns a zero area if the container has no widget. -func (c *Container) widgetArea() (image.Rectangle, error) { - if !c.hasWidget() { - return image.ZR, nil - } - - padded, err := c.opts.padding.apply(c.usable()) - if err != nil { - return image.ZR, err - } - wOpts := c.opts.widget.Options() - - adjusted := padded - if maxX := wOpts.MaximumSize.X; maxX > 0 && adjusted.Dx() > maxX { - adjusted.Max.X -= adjusted.Dx() - maxX - } - if maxY := wOpts.MaximumSize.Y; maxY > 0 && adjusted.Dy() > maxY { - adjusted.Max.Y -= adjusted.Dy() - maxY - } - - if wOpts.Ratio.X > 0 && wOpts.Ratio.Y > 0 { - adjusted = area.WithRatio(adjusted, wOpts.Ratio) - } - aligned, err := alignfor.Rectangle(padded, adjusted, c.opts.hAlign, c.opts.vAlign) - if err != nil { - return image.ZR, err - } - return aligned, nil -} - -// split splits the container's usable area into child areas. -// Panics if the container isn't configured for a split. -func (c *Container) split() (image.Rectangle, image.Rectangle, error) { - ar, err := c.opts.padding.apply(c.usable()) - if err != nil { - return image.ZR, image.ZR, err - } - if c.opts.splitFixed > DefaultSplitFixed { - if c.opts.split == splitTypeVertical { - return area.VSplitCells(ar, c.opts.splitFixed) - } - return area.HSplitCells(ar, c.opts.splitFixed) - } - - if c.opts.split == splitTypeVertical { - return area.VSplit(ar, c.opts.splitPercent) - } - return area.HSplit(ar, c.opts.splitPercent) -} - -// createFirst creates and returns the first sub container of this container. -func (c *Container) createFirst(opts []Option) error { - first, err := newChild(c, opts) - if err != nil { - return err - } - c.first = first - return nil -} - -// createSecond creates and returns the second sub container of this container. -func (c *Container) createSecond(opts []Option) error { - second, err := newChild(c, opts) - if err != nil { - return err - } - c.second = second - return nil -} - -// Draw draws this container and all of its sub containers. -func (c *Container) Draw() error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.clearNeeded { - if err := c.term.Clear(); err != nil { - return fmt.Errorf("term.Clear => error: %v", err) - } - c.clearNeeded = false - } - - // Update the area we are tracking for focus in case the terminal size - // changed. - ar, err := area.FromSize(c.term.Size()) - if err != nil { - return err - } - c.focusTracker.updateArea(ar) - return drawTree(c) -} - -// Update updates container with the specified id by setting the provided -// options. This can be used to perform dynamic layout changes, i.e. anything -// between replacing the widget in the container and completely changing the -// layout and splits. -// The argument id must match exactly one container with that was created with -// matching ID() option. The argument id must not be an empty string. -func (c *Container) Update(id string, opts ...Option) error { - c.mu.Lock() - defer c.mu.Unlock() - - target, err := findID(c, id) - if err != nil { - return err - } - c.clearNeeded = true - - if err := applyOptions(target, opts...); err != nil { - return err - } - if err := validateOptions(c); err != nil { - return err - } - - // The currently focused container might not be reachable anymore, because - // it was under the target. If that is so, move the focus up to the target. - if !c.focusTracker.reachableFrom(c) { - c.focusTracker.setActive(target) - } - return nil -} - -// updateFocus processes the mouse event and determines if it changes the -// focused container. -// Caller must hold c.mu. -func (c *Container) updateFocus(m *terminalapi.Mouse) { - target := pointCont(c, m.Position) - if target == nil { // Ignore mouse clicks where no containers are. - return - } - c.focusTracker.mouse(target, m) -} - -// processEvent processes events delivered to the container. -func (c *Container) processEvent(ev terminalapi.Event) error { - // This is done in two stages. - // 1) under lock we traverse the container and identify all targets - // (widgets) that should receive the event. - // 2) lock is released and events are delivered to the widgets. Widgets - // themselves are thread-safe. Lock must be releases when delivering, - // because some widgets might try to mutate the container when they - // receive the event, like dynamically change the layout. - c.mu.Lock() - sendFn, err := c.prepareEvTargets(ev) - c.mu.Unlock() - if err != nil { - return err - } - return sendFn() -} - -// prepareEvTargets returns a closure, that when called delivers the event to -// widgets that registered for it. -// Also processes the event on behalf of the container (tracks keyboard focus). -// Caller must hold c.mu. -func (c *Container) prepareEvTargets(ev terminalapi.Event) (func() error, error) { - switch e := ev.(type) { - case *terminalapi.Mouse: - c.updateFocus(ev.(*terminalapi.Mouse)) - - targets, err := c.mouseEvTargets(e) - if err != nil { - return nil, err - } - return func() error { - for _, mt := range targets { - if err := mt.widget.Mouse(mt.ev); err != nil { - return err - } - } - return nil - }, nil - - case *terminalapi.Keyboard: - targets := c.keyEvTargets() - return func() error { - for _, w := range targets { - if err := w.Keyboard(e); err != nil { - return err - } - } - return nil - }, nil - - default: - return nil, fmt.Errorf("container received an unsupported event type %T", ev) - } -} - -// keyEvTargets returns those widgets found in the container that should -// receive this keyboard event. -// Caller must hold c.mu. -func (c *Container) keyEvTargets() []widgetapi.Widget { - var ( - errStr string - widgets []widgetapi.Widget - ) - - // All the widgets that should receive this event. - // For now stable ordering (preOrder). - preOrder(c, &errStr, visitFunc(func(cur *Container) error { - if !cur.hasWidget() { - return nil - } - - wOpt := cur.opts.widget.Options() - switch wOpt.WantKeyboard { - case widgetapi.KeyScopeNone: - // Widget doesn't want any keyboard events. - return nil - - case widgetapi.KeyScopeFocused: - if cur.focusTracker.isActive(cur) { - widgets = append(widgets, cur.opts.widget) - } - - case widgetapi.KeyScopeGlobal: - widgets = append(widgets, cur.opts.widget) - } - return nil - })) - return widgets -} - -// mouseEvTarget contains a mouse event adjusted relative to the widget's area -// and the widget that should receive it. -type mouseEvTarget struct { - // widget is the widget that should receive the mouse event. - widget widgetapi.Widget - // ev is the adjusted mouse event. - ev *terminalapi.Mouse -} - -// newMouseEvTarget returns a new newMouseEvTarget. -func newMouseEvTarget(w widgetapi.Widget, wArea image.Rectangle, ev *terminalapi.Mouse) *mouseEvTarget { - return &mouseEvTarget{ - widget: w, - ev: adjustMouseEv(ev, wArea), - } -} - -// mouseEvTargets returns those widgets found in the container that should -// receive this mouse event. -// Caller must hold c.mu. -func (c *Container) mouseEvTargets(m *terminalapi.Mouse) ([]*mouseEvTarget, error) { - var ( - errStr string - widgets []*mouseEvTarget - ) - - // All the widgets that should receive this event. - // For now stable ordering (preOrder). - preOrder(c, &errStr, visitFunc(func(cur *Container) error { - if !cur.hasWidget() { - return nil - } - - wOpts := cur.opts.widget.Options() - wa, err := cur.widgetArea() - if err != nil { - return err - } - - switch wOpts.WantMouse { - case widgetapi.MouseScopeNone: - // Widget doesn't want any mouse events. - return nil - - case widgetapi.MouseScopeWidget: - // Only if the event falls inside of the widget's canvas. - if m.Position.In(wa) { - widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m)) - } - - case widgetapi.MouseScopeContainer: - // Only if the event falls inside the widget's parent container. - if m.Position.In(cur.area) { - widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m)) - } - - case widgetapi.MouseScopeGlobal: - // Widget wants all mouse events. - widgets = append(widgets, newMouseEvTarget(cur.opts.widget, wa, m)) - } - return nil - })) - - if errStr != "" { - return nil, errors.New(errStr) - } - return widgets, nil -} - -// Subscribe tells the container to subscribe itself and widgets to the -// provided event distribution system. -// This method is private to termdash, stability isn't guaranteed and changes -// won't be backward compatible. -func (c *Container) Subscribe(eds *event.DistributionSystem) { - c.mu.Lock() - defer c.mu.Unlock() - - // maxReps is the maximum number of repetitive events towards widgets - // before we throttle them. - const maxReps = 10 - - // Subscriber the container itself in order to track keyboard focus. - want := []terminalapi.Event{ - &terminalapi.Keyboard{}, - &terminalapi.Mouse{}, - } - eds.Subscribe(want, func(ev terminalapi.Event) { - if err := c.processEvent(ev); err != nil { - eds.Event(terminalapi.NewErrorf("failed to process event %v: %v", ev, err)) - } - }, event.MaxRepetitive(maxReps)) -} - -// adjustMouseEv adjusts the mouse event relative to the widget area. -func adjustMouseEv(m *terminalapi.Mouse, wArea image.Rectangle) *terminalapi.Mouse { - // The sent mouse coordinate is relative to the widget canvas, i.e. zero - // based, even though the widget might not be in the top left corner on the - // terminal. - offset := wArea.Min - if m.Position.In(wArea) { - return &terminalapi.Mouse{ - Position: m.Position.Sub(offset), - Button: m.Button, - } - } - return &terminalapi.Mouse{ - Position: image.Point{-1, -1}, - Button: m.Button, - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go deleted file mode 100644 index d186b1272..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/draw.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package container - -// draw.go contains logic to draw containers and the contained widgets. - -import ( - "errors" - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/area" - "github.com/mum4k/termdash/private/canvas" - "github.com/mum4k/termdash/private/draw" - "github.com/mum4k/termdash/widgetapi" -) - -// drawTree draws this container and all of its sub containers. -func drawTree(c *Container) error { - var errStr string - - root := rootCont(c) - size := root.term.Size() - ar, err := root.opts.margin.apply(image.Rect(0, 0, size.X, size.Y)) - if err != nil { - return err - } - root.area = ar - - preOrder(root, &errStr, visitFunc(func(c *Container) error { - first, second, err := c.split() - if err != nil { - return err - } - if c.first != nil { - ar, err := c.first.opts.margin.apply(first) - if err != nil { - return err - } - c.first.area = ar - } - - if c.second != nil { - ar, err := c.second.opts.margin.apply(second) - if err != nil { - return err - } - c.second.area = ar - } - return drawCont(c) - })) - if errStr != "" { - return errors.New(errStr) - } - return nil -} - -// drawBorder draws the border around the container if requested. -func drawBorder(c *Container) error { - if !c.hasBorder() { - return nil - } - - cvs, err := canvas.New(c.area) - if err != nil { - return err - } - - ar, err := area.FromSize(cvs.Size()) - if err != nil { - return err - } - - var cOpts []cell.Option - if c.focusTracker.isActive(c) { - cOpts = append(cOpts, cell.FgColor(c.opts.inherited.focusedColor)) - } else { - cOpts = append(cOpts, cell.FgColor(c.opts.inherited.borderColor)) - } - - if err := draw.Border(cvs, ar, - draw.BorderLineStyle(c.opts.border), - draw.BorderTitle(c.opts.borderTitle, draw.OverrunModeThreeDot, cOpts...), - draw.BorderTitleAlign(c.opts.borderTitleHAlign), - draw.BorderCellOpts(cOpts...), - ); err != nil { - return err - } - return cvs.Apply(c.term) -} - -// drawWidget requests the widget to draw on the canvas. -func drawWidget(c *Container) error { - widgetArea, err := c.widgetArea() - if err != nil { - return err - } - if widgetArea == image.ZR { - return nil - } - - if !c.hasWidget() { - return nil - } - - needSize := image.Point{1, 1} - wOpts := c.opts.widget.Options() - if wOpts.MinimumSize.X > 0 && wOpts.MinimumSize.Y > 0 { - needSize = wOpts.MinimumSize - } - - if widgetArea.Dx() < needSize.X || widgetArea.Dy() < needSize.Y { - return drawResize(c, c.usable()) - } - - cvs, err := canvas.New(widgetArea) - if err != nil { - return err - } - - meta := &widgetapi.Meta{ - Focused: c.focusTracker.isActive(c), - } - - if err := c.opts.widget.Draw(cvs, meta); err != nil { - return err - } - return cvs.Apply(c.term) -} - -// drawResize draws an unicode character indicating that the size is too small to draw this container. -// Does nothing if the size is smaller than one cell, leaving no space for the character. -func drawResize(c *Container, area image.Rectangle) error { - if area.Dx() < 1 || area.Dy() < 1 { - return nil - } - - cvs, err := canvas.New(area) - if err != nil { - return err - } - if err := draw.ResizeNeeded(cvs); err != nil { - return err - } - return cvs.Apply(c.term) -} - -// drawCont draws the container and its widget. -func drawCont(c *Container) error { - if us := c.usable(); us.Dx() <= 0 || us.Dy() <= 0 { - return drawResize(c, c.area) - } - - if err := drawBorder(c); err != nil { - return fmt.Errorf("unable to draw container border: %v", err) - } - - if err := drawWidget(c); err != nil { - return fmt.Errorf("unable to draw widget %T: %v", c.opts.widget, err) - } - return nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go deleted file mode 100644 index 4320eea73..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/focus.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package container - -// focus.go contains code that tracks the focused container. - -import ( - "image" - - "github.com/mum4k/termdash/mouse" - "github.com/mum4k/termdash/private/button" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// pointCont finds the top-most (on the screen) container whose area contains -// the given point. Returns nil if none of the containers in the tree contain -// this point. -func pointCont(c *Container, p image.Point) *Container { - var ( - errStr string - cont *Container - ) - postOrder(rootCont(c), &errStr, visitFunc(func(c *Container) error { - if p.In(c.area) && cont == nil { - cont = c - } - return nil - })) - return cont -} - -// focusTracker tracks the active (focused) container. -// This is not thread-safe, the implementation assumes that the owner of -// focusTracker performs locking. -type focusTracker struct { - // container is the currently focused container. - container *Container - - // candidate is the container that might become focused next. I.e. we got - // a mouse click and now waiting for a release or a timeout. - candidate *Container - - // buttonFSM is a state machine tracking mouse clicks in containers and - // moving focus from one container to the next. - buttonFSM *button.FSM -} - -// newFocusTracker returns a new focus tracker with focus set at the provided -// container. -func newFocusTracker(c *Container) *focusTracker { - return &focusTracker{ - container: c, - // Mouse FSM tracking clicks inside the entire area for the root - // container. - buttonFSM: button.NewFSM(mouse.ButtonLeft, c.area), - } -} - -// isActive determines if the provided container is the currently active container. -func (ft *focusTracker) isActive(c *Container) bool { - return ft.container == c -} - -// setActive sets the currently active container to the one provided. -func (ft *focusTracker) setActive(c *Container) { - ft.container = c -} - -// mouse identifies mouse events that change the focused container and track -// the focused container in the tree. -// The argument c is the container onto which the mouse event landed. -func (ft *focusTracker) mouse(target *Container, m *terminalapi.Mouse) { - clicked, bs := ft.buttonFSM.Event(m) - switch { - case bs == button.Down: - ft.candidate = target - case bs == button.Up && clicked: - if target == ft.candidate { - ft.container = target - } - } -} - -// updateArea updates the area that the focus tracker considers active for -// mouse clicks. -func (ft *focusTracker) updateArea(ar image.Rectangle) { - ft.buttonFSM.UpdateArea(ar) -} - -// reachableFrom asserts whether the currently focused container is reachable -// from the provided node in the tree. -func (ft *focusTracker) reachableFrom(node *Container) bool { - var ( - errStr string - reachable bool - ) - preOrder(node, &errStr, visitFunc(func(c *Container) error { - if c == ft.container { - reachable = true - } - return nil - })) - return reachable -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go deleted file mode 100644 index 2d34af4db..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/options.go +++ /dev/null @@ -1,817 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package container - -// options.go defines container options. - -import ( - "errors" - "fmt" - "image" - - "github.com/mum4k/termdash/align" - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/linestyle" - "github.com/mum4k/termdash/private/area" - "github.com/mum4k/termdash/widgetapi" -) - -// applyOptions applies the options to the container and validates them. -func applyOptions(c *Container, opts ...Option) error { - for _, opt := range opts { - if err := opt.set(c); err != nil { - return err - } - } - return nil -} - -// ensure all the container identifiers are either empty or unique. -func validateIds(c *Container, seen map[string]bool) error { - if c.opts.id == "" { - return nil - } else if seen[c.opts.id] { - return fmt.Errorf("duplicate container ID %q", c.opts.id) - } - seen[c.opts.id] = true - - return nil -} - -// ensure all the container only have one split modifier. -func validateSplits(c *Container) error { - if c.opts.splitFixed > DefaultSplitFixed && c.opts.splitPercent != DefaultSplitPercent { - return fmt.Errorf( - "only one of splitFixed `%v` and splitPercent `%v` is allowed to be set per container", - c.opts.splitFixed, - c.opts.splitPercent, - ) - } - - return nil -} - -// validateOptions validates options set in the container tree. -func validateOptions(c *Container) error { - var errStr string - seenID := map[string]bool{} - preOrder(c, &errStr, func(c *Container) error { - if err := validateIds(c, seenID); err != nil { - return err - } - if err := validateSplits(c); err != nil { - return err - } - - return nil - }) - if errStr != "" { - return errors.New(errStr) - } - - return nil -} - -// Option is used to provide options to a container. -type Option interface { - // set sets the provided option. - set(*Container) error -} - -// options stores the options provided to the container. -type options struct { - // id is the identifier provided by the user. - id string - - // inherited are options that are inherited by child containers. - inherited inherited - - // split identifies how is this container split. - split splitType - splitPercent int - splitFixed int - - // widget is the widget in the container. - // A container can have either two sub containers (left and right) or a - // widget. But not both. - widget widgetapi.Widget - - // Alignment of the widget if present. - hAlign align.Horizontal - vAlign align.Vertical - - // border is the border around the container. - border linestyle.LineStyle - borderTitle string - borderTitleHAlign align.Horizontal - - // padding is a space reserved between the outer edge of the container and - // its content (the widget or other sub-containers). - padding padding - - // margin is a space reserved on the outside of the container. - margin margin -} - -// margin stores the configured margin for the container. -// For each margin direction, only one of the percentage or cells is set. -type margin struct { - topCells int - topPerc int - rightCells int - rightPerc int - bottomCells int - bottomPerc int - leftCells int - leftPerc int -} - -// apply applies the configured margin to the area. -func (p *margin) apply(ar image.Rectangle) (image.Rectangle, error) { - switch { - case p.topCells != 0 || p.rightCells != 0 || p.bottomCells != 0 || p.leftCells != 0: - return area.Shrink(ar, p.topCells, p.rightCells, p.bottomCells, p.leftCells) - case p.topPerc != 0 || p.rightPerc != 0 || p.bottomPerc != 0 || p.leftPerc != 0: - return area.ShrinkPercent(ar, p.topPerc, p.rightPerc, p.bottomPerc, p.leftPerc) - } - return ar, nil -} - -// padding stores the configured padding for the container. -// For each padding direction, only one of the percentage or cells is set. -type padding struct { - topCells int - topPerc int - rightCells int - rightPerc int - bottomCells int - bottomPerc int - leftCells int - leftPerc int -} - -// apply applies the configured padding to the area. -func (p *padding) apply(ar image.Rectangle) (image.Rectangle, error) { - switch { - case p.topCells != 0 || p.rightCells != 0 || p.bottomCells != 0 || p.leftCells != 0: - return area.Shrink(ar, p.topCells, p.rightCells, p.bottomCells, p.leftCells) - case p.topPerc != 0 || p.rightPerc != 0 || p.bottomPerc != 0 || p.leftPerc != 0: - return area.ShrinkPercent(ar, p.topPerc, p.rightPerc, p.bottomPerc, p.leftPerc) - } - return ar, nil -} - -// inherited contains options that are inherited by child containers. -type inherited struct { - // borderColor is the color used for the border. - borderColor cell.Color - // focusedColor is the color used for the border when focused. - focusedColor cell.Color -} - -// newOptions returns a new options instance with the default values. -// Parent are the inherited options from the parent container or nil if these -// options are for a container with no parent (the root). -func newOptions(parent *options) *options { - opts := &options{ - inherited: inherited{ - focusedColor: cell.ColorYellow, - }, - hAlign: align.HorizontalCenter, - vAlign: align.VerticalMiddle, - splitPercent: DefaultSplitPercent, - splitFixed: DefaultSplitFixed, - } - if parent != nil { - opts.inherited = parent.inherited - } - return opts -} - -// option implements Option. -type option func(*Container) error - -// set implements Option.set. -func (o option) set(c *Container) error { - return o(c) -} - -// SplitOption is used when splitting containers. -type SplitOption interface { - // setSplit sets the provided split option. - setSplit(*options) error -} - -// splitOption implements SplitOption. -type splitOption func(*options) error - -// setSplit implements SplitOption.setSplit. -func (so splitOption) setSplit(opts *options) error { - return so(opts) -} - -// DefaultSplitPercent is the default value for the SplitPercent option. -const DefaultSplitPercent = 50 - -// DefaultSplitFixed is the default value for the SplitFixed option. -const DefaultSplitFixed = -1 - -// SplitPercent sets the relative size of the split as percentage of the available space. -// When using SplitVertical, the provided size is applied to the new left -// container, the new right container gets the reminder of the size. -// When using SplitHorizontal, the provided size is applied to the new top -// container, the new bottom container gets the reminder of the size. -// The provided value must be a positive number in the range 0 < p < 100. -// If not provided, defaults to DefaultSplitPercent. -func SplitPercent(p int) SplitOption { - return splitOption(func(opts *options) error { - if min, max := 0, 100; p <= min || p >= max { - return fmt.Errorf("invalid split percentage %d, must be in range %d < p < %d", p, min, max) - } - opts.splitPercent = p - return nil - }) -} - -// SplitFixed sets the size of the first container to be a fixed value -// and makes the second container take up the remaining space. -// When using SplitVertical, the provided size is applied to the new left -// container, the new right container gets the reminder of the size. -// When using SplitHorizontal, the provided size is applied to the new top -// container, the new bottom container gets the reminder of the size. -// The provided value must be a positive number in the range 0 <= cells. -// If SplitFixed() is not specified, it defaults to SplitPercent() and its given value. -// Only one of SplitFixed() and SplitPercent() can be specified per container. -func SplitFixed(cells int) SplitOption { - return splitOption(func(opts *options) error { - if cells < 0 { - return fmt.Errorf("invalid fixed value %d, must be in range %d <= cells", cells, 0) - } - opts.splitFixed = cells - return nil - }) -} - -// SplitVertical splits the container along the vertical axis into two sub -// containers. The use of this option removes any widget placed at this -// container, containers with sub containers cannot contain widgets. -func SplitVertical(l LeftOption, r RightOption, opts ...SplitOption) Option { - return option(func(c *Container) error { - c.opts.split = splitTypeVertical - c.opts.widget = nil - for _, opt := range opts { - if err := opt.setSplit(c.opts); err != nil { - return err - } - } - - if err := c.createFirst(l.lOpts()); err != nil { - return err - } - return c.createSecond(r.rOpts()) - }) -} - -// SplitHorizontal splits the container along the horizontal axis into two sub -// containers. The use of this option removes any widget placed at this -// container, containers with sub containers cannot contain widgets. -func SplitHorizontal(t TopOption, b BottomOption, opts ...SplitOption) Option { - return option(func(c *Container) error { - c.opts.split = splitTypeHorizontal - c.opts.widget = nil - for _, opt := range opts { - if err := opt.setSplit(c.opts); err != nil { - return err - } - } - - if err := c.createFirst(t.tOpts()); err != nil { - return err - } - - return c.createSecond(b.bOpts()) - }) -} - -// ID sets an identifier for this container. -// This ID can be later used to perform dynamic layout changes by passing new -// options to this container. When provided, it must be a non-empty string that -// is unique among all the containers. -func ID(id string) Option { - return option(func(c *Container) error { - if id == "" { - return errors.New("the ID cannot be an empty string") - } - c.opts.id = id - return nil - }) -} - -// Clear clears this container. -// If the container contains a widget, the widget is removed. -// If the container had any sub containers or splits, they are removed. -func Clear() Option { - return option(func(c *Container) error { - c.opts.widget = nil - c.first = nil - c.second = nil - return nil - }) -} - -// PlaceWidget places the provided widget into the container. -// The use of this option removes any sub containers. Containers with sub -// containers cannot have widgets. -func PlaceWidget(w widgetapi.Widget) Option { - return option(func(c *Container) error { - c.opts.widget = w - c.first = nil - c.second = nil - return nil - }) -} - -// MarginTop sets reserved space outside of the container at its top. -// The provided number is the absolute margin in cells and must be zero or a -// positive integer. Only one of MarginTop or MarginTopPercent can be specified. -func MarginTop(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid MarginTop(%d), must be in range %d <= value", cells, min) - } - if c.opts.margin.topPerc > 0 { - return fmt.Errorf("cannot specify both MarginTop(%d) and MarginTopPercent(%d)", cells, c.opts.margin.topPerc) - } - c.opts.margin.topCells = cells - return nil - }) -} - -// MarginRight sets reserved space outside of the container at its right. -// The provided number is the absolute margin in cells and must be zero or a -// positive integer. Only one of MarginRight or MarginRightPercent can be specified. -func MarginRight(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid MarginRight(%d), must be in range %d <= value", cells, min) - } - if c.opts.margin.rightPerc > 0 { - return fmt.Errorf("cannot specify both MarginRight(%d) and MarginRightPercent(%d)", cells, c.opts.margin.rightPerc) - } - c.opts.margin.rightCells = cells - return nil - }) -} - -// MarginBottom sets reserved space outside of the container at its bottom. -// The provided number is the absolute margin in cells and must be zero or a -// positive integer. Only one of MarginBottom or MarginBottomPercent can be specified. -func MarginBottom(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid MarginBottom(%d), must be in range %d <= value", cells, min) - } - if c.opts.margin.bottomPerc > 0 { - return fmt.Errorf("cannot specify both MarginBottom(%d) and MarginBottomPercent(%d)", cells, c.opts.margin.bottomPerc) - } - c.opts.margin.bottomCells = cells - return nil - }) -} - -// MarginLeft sets reserved space outside of the container at its left. -// The provided number is the absolute margin in cells and must be zero or a -// positive integer. Only one of MarginLeft or MarginLeftPercent can be specified. -func MarginLeft(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid MarginLeft(%d), must be in range %d <= value", cells, min) - } - if c.opts.margin.leftPerc > 0 { - return fmt.Errorf("cannot specify both MarginLeft(%d) and MarginLeftPercent(%d)", cells, c.opts.margin.leftPerc) - } - c.opts.margin.leftCells = cells - return nil - }) -} - -// MarginTopPercent sets reserved space outside of the container at its top. -// The provided number is a relative margin defined as percentage of the container's height. -// Only one of MarginTop or MarginTopPercent can be specified. -// The value must be in range 0 <= value <= 100. -func MarginTopPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid MarginTopPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.margin.topCells > 0 { - return fmt.Errorf("cannot specify both MarginTopPercent(%d) and MarginTop(%d)", perc, c.opts.margin.topCells) - } - c.opts.margin.topPerc = perc - return nil - }) -} - -// MarginRightPercent sets reserved space outside of the container at its right. -// The provided number is a relative margin defined as percentage of the container's height. -// Only one of MarginRight or MarginRightPercent can be specified. -// The value must be in range 0 <= value <= 100. -func MarginRightPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid MarginRightPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.margin.rightCells > 0 { - return fmt.Errorf("cannot specify both MarginRightPercent(%d) and MarginRight(%d)", perc, c.opts.margin.rightCells) - } - c.opts.margin.rightPerc = perc - return nil - }) -} - -// MarginBottomPercent sets reserved space outside of the container at its bottom. -// The provided number is a relative margin defined as percentage of the container's height. -// Only one of MarginBottom or MarginBottomPercent can be specified. -// The value must be in range 0 <= value <= 100. -func MarginBottomPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid MarginBottomPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.margin.bottomCells > 0 { - return fmt.Errorf("cannot specify both MarginBottomPercent(%d) and MarginBottom(%d)", perc, c.opts.margin.bottomCells) - } - c.opts.margin.bottomPerc = perc - return nil - }) -} - -// MarginLeftPercent sets reserved space outside of the container at its left. -// The provided number is a relative margin defined as percentage of the container's height. -// Only one of MarginLeft or MarginLeftPercent can be specified. -// The value must be in range 0 <= value <= 100. -func MarginLeftPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid MarginLeftPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.margin.leftCells > 0 { - return fmt.Errorf("cannot specify both MarginLeftPercent(%d) and MarginLeft(%d)", perc, c.opts.margin.leftCells) - } - c.opts.margin.leftPerc = perc - return nil - }) -} - -// PaddingTop sets reserved space between container and the top side of its widget. -// The widget's area size is decreased to accommodate the padding. -// The provided number is the absolute padding in cells and must be zero or a -// positive integer. Only one of PaddingTop or PaddingTopPercent can be specified. -func PaddingTop(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid PaddingTop(%d), must be in range %d <= value", cells, min) - } - if c.opts.padding.topPerc > 0 { - return fmt.Errorf("cannot specify both PaddingTop(%d) and PaddingTopPercent(%d)", cells, c.opts.padding.topPerc) - } - c.opts.padding.topCells = cells - return nil - }) -} - -// PaddingRight sets reserved space between container and the right side of its widget. -// The widget's area size is decreased to accommodate the padding. -// The provided number is the absolute padding in cells and must be zero or a -// positive integer. Only one of PaddingRight or PaddingRightPercent can be specified. -func PaddingRight(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid PaddingRight(%d), must be in range %d <= value", cells, min) - } - if c.opts.padding.rightPerc > 0 { - return fmt.Errorf("cannot specify both PaddingRight(%d) and PaddingRightPercent(%d)", cells, c.opts.padding.rightPerc) - } - c.opts.padding.rightCells = cells - return nil - }) -} - -// PaddingBottom sets reserved space between container and the bottom side of its widget. -// The widget's area size is decreased to accommodate the padding. -// The provided number is the absolute padding in cells and must be zero or a -// positive integer. Only one of PaddingBottom or PaddingBottomPercent can be specified. -func PaddingBottom(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid PaddingBottom(%d), must be in range %d <= value", cells, min) - } - if c.opts.padding.bottomPerc > 0 { - return fmt.Errorf("cannot specify both PaddingBottom(%d) and PaddingBottomPercent(%d)", cells, c.opts.padding.bottomPerc) - } - c.opts.padding.bottomCells = cells - return nil - }) -} - -// PaddingLeft sets reserved space between container and the left side of its widget. -// The widget's area size is decreased to accommodate the padding. -// The provided number is the absolute padding in cells and must be zero or a -// positive integer. Only one of PaddingLeft or PaddingLeftPercent can be specified. -func PaddingLeft(cells int) Option { - return option(func(c *Container) error { - if min := 0; cells < min { - return fmt.Errorf("invalid PaddingLeft(%d), must be in range %d <= value", cells, min) - } - if c.opts.padding.leftPerc > 0 { - return fmt.Errorf("cannot specify both PaddingLeft(%d) and PaddingLeftPercent(%d)", cells, c.opts.padding.leftPerc) - } - c.opts.padding.leftCells = cells - return nil - }) -} - -// PaddingTopPercent sets reserved space between container and the top side of -// its widget. The widget's area size is decreased to accommodate the padding. -// The provided number is a relative padding defined as percentage of the -// container's height. The value must be in range 0 <= value <= 100. -// Only one of PaddingTop or PaddingTopPercent can be specified. -func PaddingTopPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid PaddingTopPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.padding.topCells > 0 { - return fmt.Errorf("cannot specify both PaddingTopPercent(%d) and PaddingTop(%d)", perc, c.opts.padding.topCells) - } - c.opts.padding.topPerc = perc - return nil - }) -} - -// PaddingRightPercent sets reserved space between container and the right side of -// its widget. The widget's area size is decreased to accommodate the padding. -// The provided number is a relative padding defined as percentage of the -// container's width. The value must be in range 0 <= value <= 100. -// Only one of PaddingRight or PaddingRightPercent can be specified. -func PaddingRightPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid PaddingRightPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.padding.rightCells > 0 { - return fmt.Errorf("cannot specify both PaddingRightPercent(%d) and PaddingRight(%d)", perc, c.opts.padding.rightCells) - } - c.opts.padding.rightPerc = perc - return nil - }) -} - -// PaddingBottomPercent sets reserved space between container and the bottom side of -// its widget. The widget's area size is decreased to accommodate the padding. -// The provided number is a relative padding defined as percentage of the -// container's height. The value must be in range 0 <= value <= 100. -// Only one of PaddingBottom or PaddingBottomPercent can be specified. -func PaddingBottomPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid PaddingBottomPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.padding.bottomCells > 0 { - return fmt.Errorf("cannot specify both PaddingBottomPercent(%d) and PaddingBottom(%d)", perc, c.opts.padding.bottomCells) - } - c.opts.padding.bottomPerc = perc - return nil - }) -} - -// PaddingLeftPercent sets reserved space between container and the left side of -// its widget. The widget's area size is decreased to accommodate the padding. -// The provided number is a relative padding defined as percentage of the -// container's width. The value must be in range 0 <= value <= 100. -// Only one of PaddingLeft or PaddingLeftPercent can be specified. -func PaddingLeftPercent(perc int) Option { - return option(func(c *Container) error { - if min, max := 0, 100; perc < min || perc > max { - return fmt.Errorf("invalid PaddingLeftPercent(%d), must be in range %d <= value <= %d", perc, min, max) - } - if c.opts.padding.leftCells > 0 { - return fmt.Errorf("cannot specify both PaddingLeftPercent(%d) and PaddingLeft(%d)", perc, c.opts.padding.leftCells) - } - c.opts.padding.leftPerc = perc - return nil - }) -} - -// AlignHorizontal sets the horizontal alignment for the widget placed in the -// container. Has no effect if the container contains no widget. -// Defaults to alignment in the center. -func AlignHorizontal(h align.Horizontal) Option { - return option(func(c *Container) error { - c.opts.hAlign = h - return nil - }) -} - -// AlignVertical sets the vertical alignment for the widget placed in the container. -// Has no effect if the container contains no widget. -// Defaults to alignment in the middle. -func AlignVertical(v align.Vertical) Option { - return option(func(c *Container) error { - c.opts.vAlign = v - return nil - }) -} - -// Border configures the container to have a border of the specified style. -func Border(ls linestyle.LineStyle) Option { - return option(func(c *Container) error { - c.opts.border = ls - return nil - }) -} - -// BorderTitle sets a text title within the border. -func BorderTitle(title string) Option { - return option(func(c *Container) error { - c.opts.borderTitle = title - return nil - }) -} - -// BorderTitleAlignLeft aligns the border title on the left. -func BorderTitleAlignLeft() Option { - return option(func(c *Container) error { - c.opts.borderTitleHAlign = align.HorizontalLeft - return nil - }) -} - -// BorderTitleAlignCenter aligns the border title in the center. -func BorderTitleAlignCenter() Option { - return option(func(c *Container) error { - c.opts.borderTitleHAlign = align.HorizontalCenter - return nil - }) -} - -// BorderTitleAlignRight aligns the border title on the right. -func BorderTitleAlignRight() Option { - return option(func(c *Container) error { - c.opts.borderTitleHAlign = align.HorizontalRight - return nil - }) -} - -// BorderColor sets the color of the border around the container. -// This option is inherited to sub containers created by container splits. -func BorderColor(color cell.Color) Option { - return option(func(c *Container) error { - c.opts.inherited.borderColor = color - return nil - }) -} - -// FocusedColor sets the color of the border around the container when it has -// keyboard focus. -// This option is inherited to sub containers created by container splits. -func FocusedColor(color cell.Color) Option { - return option(func(c *Container) error { - c.opts.inherited.focusedColor = color - return nil - }) -} - -// splitType identifies how a container is split. -type splitType int - -// String implements fmt.Stringer() -func (st splitType) String() string { - if n, ok := splitTypeNames[st]; ok { - return n - } - return "splitTypeUnknown" -} - -// splitTypeNames maps splitType values to human readable names. -var splitTypeNames = map[splitType]string{ - splitTypeVertical: "splitTypeVertical", - splitTypeHorizontal: "splitTypeHorizontal", -} - -const ( - splitTypeVertical splitType = iota - splitTypeHorizontal -) - -// LeftOption is used to provide options to the left sub container after a -// vertical split of the parent. -type LeftOption interface { - // lOpts returns the options. - lOpts() []Option -} - -// leftOption implements LeftOption. -type leftOption func() []Option - -// lOpts implements LeftOption.lOpts. -func (lo leftOption) lOpts() []Option { - if lo == nil { - return nil - } - return lo() -} - -// Left applies options to the left sub container after a vertical split of the parent. -func Left(opts ...Option) LeftOption { - return leftOption(func() []Option { - return opts - }) -} - -// RightOption is used to provide options to the right sub container after a -// vertical split of the parent. -type RightOption interface { - // rOpts returns the options. - rOpts() []Option -} - -// rightOption implements RightOption. -type rightOption func() []Option - -// rOpts implements RightOption.rOpts. -func (lo rightOption) rOpts() []Option { - if lo == nil { - return nil - } - return lo() -} - -// Right applies options to the right sub container after a vertical split of the parent. -func Right(opts ...Option) RightOption { - return rightOption(func() []Option { - return opts - }) -} - -// TopOption is used to provide options to the top sub container after a -// horizontal split of the parent. -type TopOption interface { - // tOpts returns the options. - tOpts() []Option -} - -// topOption implements TopOption. -type topOption func() []Option - -// tOpts implements TopOption.tOpts. -func (lo topOption) tOpts() []Option { - if lo == nil { - return nil - } - return lo() -} - -// Top applies options to the top sub container after a horizontal split of the parent. -func Top(opts ...Option) TopOption { - return topOption(func() []Option { - return opts - }) -} - -// BottomOption is used to provide options to the bottom sub container after a -// horizontal split of the parent. -type BottomOption interface { - // bOpts returns the options. - bOpts() []Option -} - -// bottomOption implements BottomOption. -type bottomOption func() []Option - -// bOpts implements BottomOption.bOpts. -func (lo bottomOption) bOpts() []Option { - if lo == nil { - return nil - } - return lo() -} - -// Bottom applies options to the bottom sub container after a horizontal split of the parent. -func Bottom(opts ...Option) BottomOption { - return bottomOption(func() []Option { - return opts - }) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go b/examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go deleted file mode 100644 index f728b50ba..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/container/traversal.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package container - -import ( - "errors" - "fmt" -) - -// traversal.go provides functions that navigate the container tree. - -// rootCont returns the root container. -func rootCont(c *Container) *Container { - for p := c.parent; p != nil; p = c.parent { - c = p - } - return c -} - -// visitFunc is executed during traversals when node is visited. -// If the visit function returns an error, the traversal terminates and the -// errStr is set to the text of the returned error. -type visitFunc func(*Container) error - -// preOrder performs pre-order DFS traversal on the container tree. -func preOrder(c *Container, errStr *string, visit visitFunc) { - if c == nil || *errStr != "" { - return - } - - if err := visit(c); err != nil { - *errStr = err.Error() - return - } - preOrder(c.first, errStr, visit) - preOrder(c.second, errStr, visit) -} - -// postOrder performs post-order DFS traversal on the container tree. -func postOrder(c *Container, errStr *string, visit visitFunc) { - if c == nil || *errStr != "" { - return - } - - postOrder(c.first, errStr, visit) - postOrder(c.second, errStr, visit) - if err := visit(c); err != nil { - *errStr = err.Error() - return - } -} - -// findID finds container with the provided ID. -// Returns an error of there is no container with the specified ID. -func findID(root *Container, id string) (*Container, error) { - if id == "" { - return nil, errors.New("the container ID must not be empty") - } - - var ( - errStr string - cont *Container - ) - preOrder(root, &errStr, visitFunc(func(c *Container) error { - if c.opts.id == id { - cont = c - } - return nil - })) - if cont == nil { - return nil, fmt.Errorf("cannot find container with ID %q", id) - } - return cont, nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/go.mod b/examples/go-dashboard/src/github.com/mum4k/termdash/go.mod deleted file mode 100644 index 3a81b3235..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/mum4k/termdash - -go 1.14 - -require ( - github.com/gdamore/tcell v1.3.0 - github.com/kylelemons/godebug v1.1.0 - github.com/mattn/go-runewidth v0.0.9 - github.com/nsf/termbox-go v0.0.0-20200204031403-4d2b513ad8be -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/keyboard/keyboard.go b/examples/go-dashboard/src/github.com/mum4k/termdash/keyboard/keyboard.go deleted file mode 100644 index 3a852b326..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/keyboard/keyboard.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package keyboard defines well known keyboard keys and shortcuts. -package keyboard - -// Key represents a single button on the keyboard. -// Printable characters are set to their ASCII/Unicode rune value. -// Non-printable (control) characters are equal to one of the constants defined -// below. -type Key rune - -// String implements fmt.Stringer() -func (b Key) String() string { - if n, ok := buttonNames[b]; ok { - return n - } else if b >= 0 { - return string(b) - } - return "KeyUnknown" -} - -// buttonNames maps Key values to human readable names. -var buttonNames = map[Key]string{ - KeyF1: "KeyF1", - KeyF2: "KeyF2", - KeyF3: "KeyF3", - KeyF4: "KeyF4", - KeyF5: "KeyF5", - KeyF6: "KeyF6", - KeyF7: "KeyF7", - KeyF8: "KeyF8", - KeyF9: "KeyF9", - KeyF10: "KeyF10", - KeyF11: "KeyF11", - KeyF12: "KeyF12", - KeyInsert: "KeyInsert", - KeyDelete: "KeyDelete", - KeyHome: "KeyHome", - KeyEnd: "KeyEnd", - KeyPgUp: "KeyPgUp", - KeyPgDn: "KeyPgDn", - KeyArrowUp: "KeyArrowUp", - KeyArrowDown: "KeyArrowDown", - KeyArrowLeft: "KeyArrowLeft", - KeyArrowRight: "KeyArrowRight", - KeyCtrlTilde: "KeyCtrlTilde", - KeyCtrlA: "KeyCtrlA", - KeyCtrlB: "KeyCtrlB", - KeyCtrlC: "KeyCtrlC", - KeyCtrlD: "KeyCtrlD", - KeyCtrlE: "KeyCtrlE", - KeyCtrlF: "KeyCtrlF", - KeyCtrlG: "KeyCtrlG", - KeyBackspace: "KeyBackspace", - KeyTab: "KeyTab", - KeyCtrlJ: "KeyCtrlJ", - KeyCtrlK: "KeyCtrlK", - KeyCtrlL: "KeyCtrlL", - KeyEnter: "KeyEnter", - KeyCtrlN: "KeyCtrlN", - KeyCtrlO: "KeyCtrlO", - KeyCtrlP: "KeyCtrlP", - KeyCtrlQ: "KeyCtrlQ", - KeyCtrlR: "KeyCtrlR", - KeyCtrlS: "KeyCtrlS", - KeyCtrlT: "KeyCtrlT", - KeyCtrlU: "KeyCtrlU", - KeyCtrlV: "KeyCtrlV", - KeyCtrlW: "KeyCtrlW", - KeyCtrlX: "KeyCtrlX", - KeyCtrlY: "KeyCtrlY", - KeyCtrlZ: "KeyCtrlZ", - KeyEsc: "KeyEsc", - KeyCtrl4: "KeyCtrl4", - KeyCtrl5: "KeyCtrl5", - KeyCtrl6: "KeyCtrl6", - KeyCtrl7: "KeyCtrl7", - KeySpace: "KeySpace", - KeyBackspace2: "KeyBackspace2", -} - -// Printable characters, but worth having constants for them. -const ( - KeySpace = ' ' -) - -// Negative values for non-printable characters. -const ( - KeyF1 Key = -(iota + 1) - KeyF2 - KeyF3 - KeyF4 - KeyF5 - KeyF6 - KeyF7 - KeyF8 - KeyF9 - KeyF10 - KeyF11 - KeyF12 - KeyInsert - KeyDelete - KeyHome - KeyEnd - KeyPgUp - KeyPgDn - KeyArrowUp - KeyArrowDown - KeyArrowLeft - KeyArrowRight - KeyCtrlTilde - KeyCtrlA - KeyCtrlB - KeyCtrlC - KeyCtrlD - KeyCtrlE - KeyCtrlF - KeyCtrlG - KeyBackspace - KeyTab - KeyCtrlJ - KeyCtrlK - KeyCtrlL - KeyEnter - KeyCtrlN - KeyCtrlO - KeyCtrlP - KeyCtrlQ - KeyCtrlR - KeyCtrlS - KeyCtrlT - KeyCtrlU - KeyCtrlV - KeyCtrlW - KeyCtrlX - KeyCtrlY - KeyCtrlZ - KeyEsc - KeyCtrl4 - KeyCtrl5 - KeyCtrl6 - KeyCtrl7 - KeyBackspace2 -) - -// Keys declared as duplicates by termbox. -const ( - KeyCtrl2 Key = KeyCtrlTilde - KeyCtrlSpace Key = KeyCtrlTilde - KeyCtrlH Key = KeyBackspace - KeyCtrlI Key = KeyTab - KeyCtrlM Key = KeyEnter - KeyCtrlLsqBracket Key = KeyEsc - KeyCtrl3 Key = KeyEsc - KeyCtrlBackslash Key = KeyCtrl4 - KeyCtrlRsqBracket Key = KeyCtrl5 - KeyCtrlSlash Key = KeyCtrl7 - KeyCtrlUnderscore Key = KeyCtrl7 - KeyCtrl8 Key = KeyBackspace2 -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/linestyle/linestyle.go b/examples/go-dashboard/src/github.com/mum4k/termdash/linestyle/linestyle.go deleted file mode 100644 index c34fc3959..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/linestyle/linestyle.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package linestyle defines various line styles. -package linestyle - -// LineStyle defines the supported line styles. -type LineStyle int - -// String implements fmt.Stringer() -func (ls LineStyle) String() string { - if n, ok := lineStyleNames[ls]; ok { - return n - } - return "LineStyleUnknown" -} - -// lineStyleNames maps LineStyle values to human readable names. -var lineStyleNames = map[LineStyle]string{ - None: "LineStyleNone", - Light: "LineStyleLight", - Double: "LineStyleDouble", - Round: "LineStyleRound", -} - -// Supported line styles. -// See https://en.wikipedia.org/wiki/Box-drawing_character. -const ( - // None indicates that no line should be present. - None LineStyle = iota - - // Light is line style using the '─' characters. - Light - - // Double is line style using the '═' characters. - Double - - // Round is line style using the rounded corners '╭' characters. - Round -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/mouse/mouse.go b/examples/go-dashboard/src/github.com/mum4k/termdash/mouse/mouse.go deleted file mode 100644 index d21e1d310..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/mouse/mouse.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package mouse defines known mouse buttons. -package mouse - -// Button represents a mouse button. -type Button int - -// String implements fmt.Stringer() -func (b Button) String() string { - if n, ok := buttonNames[b]; ok { - return n - } - return "ButtonUnknown" -} - -// buttonNames maps Button values to human readable names. -var buttonNames = map[Button]string{ - ButtonLeft: "ButtonLeft", - ButtonRight: "ButtonRight", - ButtonMiddle: "ButtonMiddle", - ButtonRelease: "ButtonRelease", - ButtonWheelUp: "ButtonWheelUp", - ButtonWheelDown: "ButtonWheelDown", -} - -// Buttons recognized on the mouse. -const ( - buttonUnknown Button = iota - ButtonLeft - ButtonRight - ButtonMiddle - ButtonRelease - ButtonWheelUp - ButtonWheelDown -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/alignfor/alignfor.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/alignfor/alignfor.go deleted file mode 100644 index 93cbac844..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/alignfor/alignfor.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package alignfor provides functions that align elements. -package alignfor - -import ( - "fmt" - "image" - "strings" - - "github.com/mum4k/termdash/align" - "github.com/mum4k/termdash/private/runewidth" - "github.com/mum4k/termdash/private/wrap" -) - -// hAlign aligns the given area in the rectangle horizontally. -func hAlign(rect image.Rectangle, ar image.Rectangle, h align.Horizontal) (image.Rectangle, error) { - gap := rect.Dx() - ar.Dx() - switch h { - case align.HorizontalRight: - // Use gap from above. - case align.HorizontalCenter: - gap /= 2 - case align.HorizontalLeft: - gap = 0 - default: - return image.ZR, fmt.Errorf("unsupported horizontal alignment %v", h) - } - - return image.Rect( - rect.Min.X+gap, - ar.Min.Y, - rect.Min.X+gap+ar.Dx(), - ar.Max.Y, - ), nil -} - -// vAlign aligns the given area in the rectangle vertically. -func vAlign(rect image.Rectangle, ar image.Rectangle, v align.Vertical) (image.Rectangle, error) { - gap := rect.Dy() - ar.Dy() - switch v { - case align.VerticalBottom: - // Use gap from above. - case align.VerticalMiddle: - gap /= 2 - case align.VerticalTop: - gap = 0 - default: - return image.ZR, fmt.Errorf("unsupported vertical alignment %v", v) - } - - return image.Rect( - ar.Min.X, - rect.Min.Y+gap, - ar.Max.X, - rect.Min.Y+gap+ar.Dy(), - ), nil -} - -// Rectangle aligns the area within the rectangle returning the -// aligned area. The area must fall within the rectangle. -func Rectangle(rect image.Rectangle, ar image.Rectangle, h align.Horizontal, v align.Vertical) (image.Rectangle, error) { - if !ar.In(rect) { - return image.ZR, fmt.Errorf("cannot align area %v inside rectangle %v, the area falls outside of the rectangle", ar, rect) - } - - aligned, err := hAlign(rect, ar, h) - if err != nil { - return image.ZR, err - } - aligned, err = vAlign(rect, aligned, v) - if err != nil { - return image.ZR, err - } - return aligned, nil -} - -// Text aligns the text within the given rectangle, returns the start point for the text. -// For the purposes of the alignment this assumes that text will be trimmed if -// it overruns the rectangle. -// This only supports a single line of text, the text must not contain non-printable characters, -// allows empty text. -func Text(rect image.Rectangle, text string, h align.Horizontal, v align.Vertical) (image.Point, error) { - if strings.ContainsRune(text, '\n') { - return image.ZP, fmt.Errorf("the provided text contains a newline character: %q", text) - } - - if text != "" { - if err := wrap.ValidText(text); err != nil { - return image.ZP, fmt.Errorf("the provided text contains non printable character(s): %s", err) - } - } - - cells := runewidth.StringWidth(text) - var textLen int - if cells < rect.Dx() { - textLen = cells - } else { - textLen = rect.Dx() - } - - textRect := image.Rect( - rect.Min.X, - rect.Min.Y, - // For the purposes of aligning the text, assume that it will be - // trimmed to the available space. - rect.Min.X+textLen, - rect.Min.Y+1, - ) - - aligned, err := Rectangle(rect, textRect, h, v) - if err != nil { - return image.ZP, err - } - return image.Point{aligned.Min.X, aligned.Min.Y}, nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/area/area.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/area/area.go deleted file mode 100644 index 34b21a1b5..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/area/area.go +++ /dev/null @@ -1,258 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package area provides functions working with image areas. -package area - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/private/numbers" -) - -// Size returns the size of the provided area. -func Size(area image.Rectangle) image.Point { - return image.Point{ - area.Dx(), - area.Dy(), - } -} - -// FromSize returns the corresponding area for the provided size. -func FromSize(size image.Point) (image.Rectangle, error) { - if size.X < 0 || size.Y < 0 { - return image.Rectangle{}, fmt.Errorf("cannot convert zero or negative size to an area, got: %+v", size) - } - return image.Rect(0, 0, size.X, size.Y), nil -} - -// HSplit returns two new areas created by splitting the provided area at the -// specified percentage of its width. The percentage must be in the range -// 0 <= heightPerc <= 100. -// Can return zero size areas. -func HSplit(area image.Rectangle, heightPerc int) (top image.Rectangle, bottom image.Rectangle, err error) { - if min, max := 0, 100; heightPerc < min || heightPerc > max { - return image.ZR, image.ZR, fmt.Errorf("invalid heightPerc %d, must be in range %d <= heightPerc <= %d", heightPerc, min, max) - } - height := area.Dy() * heightPerc / 100 - top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+height) - if top.Dy() == 0 { - top = image.ZR - } - bottom = image.Rect(area.Min.X, area.Min.Y+height, area.Max.X, area.Max.Y) - if bottom.Dy() == 0 { - bottom = image.ZR - } - return top, bottom, nil -} - -// VSplit returns two new areas created by splitting the provided area at the -// specified percentage of its width. The percentage must be in the range -// 0 <= widthPerc <= 100. -// Can return zero size areas. -func VSplit(area image.Rectangle, widthPerc int) (left image.Rectangle, right image.Rectangle, err error) { - if min, max := 0, 100; widthPerc < min || widthPerc > max { - return image.ZR, image.ZR, fmt.Errorf("invalid widthPerc %d, must be in range %d <= widthPerc <= %d", widthPerc, min, max) - } - width := area.Dx() * widthPerc / 100 - left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+width, area.Max.Y) - if left.Dx() == 0 { - left = image.ZR - } - right = image.Rect(area.Min.X+width, area.Min.Y, area.Max.X, area.Max.Y) - if right.Dx() == 0 { - right = image.ZR - } - return left, right, nil -} - -// VSplitCells returns two new areas created by splitting the provided area -// after the specified amount of cells of its width. The number of cells must -// be a zero or a positive integer. Providing a zero returns left=image.ZR, -// right=area. Providing a number equal or larger to area's width returns -// left=area, right=image.ZR. -func VSplitCells(area image.Rectangle, cells int) (left image.Rectangle, right image.Rectangle, err error) { - if min := 0; cells < min { - return image.ZR, image.ZR, fmt.Errorf("invalid cells %d, must be a positive integer", cells) - } - if cells == 0 { - return image.ZR, area, nil - } - - width := area.Dx() - if cells >= width { - return area, image.ZR, nil - } - - left = image.Rect(area.Min.X, area.Min.Y, area.Min.X+cells, area.Max.Y) - right = image.Rect(area.Min.X+cells, area.Min.Y, area.Max.X, area.Max.Y) - return left, right, nil -} - -// HSplitCells returns two new areas created by splitting the provided area -// after the specified amount of cells of its height. The number of cells must -// be a zero or a positive integer. Providing a zero returns top=image.ZR, -// bottom=area. Providing a number equal or larger to area's height returns -// top=area, bottom=image.ZR. -func HSplitCells(area image.Rectangle, cells int) (top image.Rectangle, bottom image.Rectangle, err error) { - if min := 0; cells < min { - return image.ZR, image.ZR, fmt.Errorf("invalid cells %d, must be a positive integer", cells) - } - if cells == 0 { - return image.ZR, area, nil - } - - height := area.Dy() - if cells >= height { - return area, image.ZR, nil - } - - top = image.Rect(area.Min.X, area.Min.Y, area.Max.X, area.Min.Y+cells) - bottom = image.Rect(area.Min.X, area.Min.Y+cells, area.Max.X, area.Max.Y) - return top, bottom, nil -} - -// ExcludeBorder returns a new area created by subtracting a border around the -// provided area. Return the zero area if there isn't enough space to exclude -// the border. -func ExcludeBorder(area image.Rectangle) image.Rectangle { - // If the area dimensions are smaller than this, subtracting a point for the - // border on each of its sides results in a zero area. - const minDim = 2 - if area.Dx() < minDim || area.Dy() < minDim { - return image.ZR - } - return image.Rect( - numbers.Abs(area.Min.X+1), - numbers.Abs(area.Min.Y+1), - numbers.Abs(area.Max.X-1), - numbers.Abs(area.Max.Y-1), - ) -} - -// WithRatio returns the largest area that has the requested ratio but is -// either equal or smaller than the provided area. Returns zero area if the -// area or the ratio are zero, or if there is no such area. -func WithRatio(area image.Rectangle, ratio image.Point) image.Rectangle { - ratio = numbers.SimplifyRatio(ratio) - if area == image.ZR || ratio == image.ZP { - return image.ZR - } - - wFact := area.Dx() / ratio.X - hFact := area.Dy() / ratio.Y - - var fact int - if wFact < hFact { - fact = wFact - } else { - fact = hFact - } - return image.Rect( - area.Min.X, - area.Min.Y, - ratio.X*fact+area.Min.X, - ratio.Y*fact+area.Min.Y, - ) -} - -// Shrink returns a new area whose size is reduced by the specified amount of -// cells. Can return a zero area if there is no space left in the area. -// The values must be zero or positive integers. -func Shrink(area image.Rectangle, topCells, rightCells, bottomCells, leftCells int) (image.Rectangle, error) { - for _, v := range []struct { - name string - value int - }{ - {"topCells", topCells}, - {"rightCells", rightCells}, - {"bottomCells", bottomCells}, - {"leftCells", leftCells}, - } { - if min := 0; v.value < min { - return image.ZR, fmt.Errorf("invalid %s(%d), must be in range %d <= value", v.name, v.value, min) - } - } - - shrunk := area - shrunk.Min.X, _ = numbers.MinMaxInts([]int{shrunk.Min.X + leftCells, shrunk.Max.X}) - _, shrunk.Max.X = numbers.MinMaxInts([]int{shrunk.Max.X - rightCells, shrunk.Min.X}) - shrunk.Min.Y, _ = numbers.MinMaxInts([]int{shrunk.Min.Y + topCells, shrunk.Max.Y}) - _, shrunk.Max.Y = numbers.MinMaxInts([]int{shrunk.Max.Y - bottomCells, shrunk.Min.Y}) - - if shrunk.Dx() == 0 || shrunk.Dy() == 0 { - return image.ZR, nil - } - return shrunk, nil -} - -// ShrinkPercent returns a new area whose size is reduced by percentage of its -// width or height. Can return a zero area if there is no space left in the area. -// The topPerc and bottomPerc indicate the percentage of area's height. -// The rightPerc and leftPerc indicate the percentage of area's width. -// The percentages must be in range 0 <= v <= 100. -func ShrinkPercent(area image.Rectangle, topPerc, rightPerc, bottomPerc, leftPerc int) (image.Rectangle, error) { - for _, v := range []struct { - name string - value int - }{ - {"topPerc", topPerc}, - {"rightPerc", rightPerc}, - {"bottomPerc", bottomPerc}, - {"leftPerc", leftPerc}, - } { - if min, max := 0, 100; v.value < min || v.value > max { - return image.ZR, fmt.Errorf("invalid %s(%d), must be in range %d <= value <= %d", v.name, v.value, min, max) - } - } - - top := area.Dy() * topPerc / 100 - bottom := area.Dy() * bottomPerc / 100 - right := area.Dx() * rightPerc / 100 - left := area.Dx() * leftPerc / 100 - return Shrink(area, top, right, bottom, left) -} - -// MoveUp returns a new area that is moved up by the specified amount of cells. -// Returns an error if the move would result in negative Y coordinates. -// The values must be zero or positive integers. -func MoveUp(area image.Rectangle, cells int) (image.Rectangle, error) { - if min := 0; cells < min { - return image.ZR, fmt.Errorf("cannot move area %v up by %d cells, must be in range %d <= value", area, cells, min) - } - - if area.Min.Y < cells { - return image.ZR, fmt.Errorf("cannot move area %v up by %d cells, would result in negative Y coordinate", area, cells) - } - - moved := area - moved.Min.Y -= cells - moved.Max.Y -= cells - return moved, nil -} - -// MoveDown returns a new area that is moved down by the specified amount of -// cells. -// The values must be zero or positive integers. -func MoveDown(area image.Rectangle, cells int) (image.Rectangle, error) { - if min := 0; cells < min { - return image.ZR, fmt.Errorf("cannot move area %v down by %d cells, must be in range %d <= value", area, cells, min) - } - - moved := area - moved.Min.Y += cells - moved.Max.Y += cells - return moved, nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go deleted file mode 100644 index d4e0601b8..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/button/button.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package button implements a state machine that tracks mouse button clicks. -package button - -import ( - "image" - - "github.com/mum4k/termdash/mouse" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// State represents the state of the mouse button. -type State int - -// String implements fmt.Stringer() -func (s State) String() string { - if n, ok := stateNames[s]; ok { - return n - } - return "StateUnknown" -} - -// stateNames maps State values to human readable names. -var stateNames = map[State]string{ - Up: "StateUp", - Down: "StateDown", -} - -const ( - // Up is the default idle state of the mouse button. - Up State = iota - - // Down is a state where the mouse button is pressed down and held. - Down -) - -// FSM implements a finite-state machine that tracks mouse clicks within an -// area. -// -// Simplifies tracking of mouse button clicks, i.e. when the caller wants to -// perform an action only if both the button press and release happen within -// the specified area. -// -// This object is not thread-safe. -type FSM struct { - // button is the mouse button whose state this FSM tracks. - button mouse.Button - - // area is the area provided to NewFSM. - area image.Rectangle - - // state is the current state of the FSM. - state stateFn -} - -// NewFSM creates a new FSM instance that tracks the state of the specified -// mouse button through button events that fall within the provided area. -func NewFSM(button mouse.Button, area image.Rectangle) *FSM { - return &FSM{ - button: button, - area: area, - state: wantPress, - } -} - -// Event is used to forward mouse events to the state machine. -// Only events related to the button specified on a call to NewFSM are -// processed. -// -// Returns a bool indicating if an action guarded by the button should be -// performed and the state of the button after the provided event. -// The bool is true if the button click should take an effect, i.e. if the -// FSM saw both the button click and its release. -func (fsm *FSM) Event(m *terminalapi.Mouse) (bool, State) { - clicked, bs, next := fsm.state(fsm, m) - fsm.state = next - return clicked, bs -} - -// UpdateArea informs FSM of an area change. -// This method is idempotent. -func (fsm *FSM) UpdateArea(area image.Rectangle) { - fsm.area = area -} - -// stateFn is a single state in the state machine. -// Returns bool indicating if a click happened, the state of the button and the -// next state of the FSM. -type stateFn func(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) - -// wantPress is the initial state, expecting a button press inside the area. -func wantPress(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) { - if m.Button != fsm.button || !m.Position.In(fsm.area) { - return false, Up, wantPress - } - return false, Down, wantRelease -} - -// wantRelease waits for a mouse button release in the same area as -// the press. -func wantRelease(fsm *FSM, m *terminalapi.Mouse) (bool, State, stateFn) { - switch m.Button { - case fsm.button: - if m.Position.In(fsm.area) { - // Remain in the same state, since termbox reports move of mouse with - // button held down as a series of clicks, one per position. - return false, Down, wantRelease - } - return false, Up, wantPress - - case mouse.ButtonRelease: - if m.Position.In(fsm.area) { - // Seen both press and release, report a click. - return true, Up, wantPress - } - // Release the button even if the release event happened outside of the area. - return false, Up, wantPress - - default: - return false, Up, wantPress - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/braille/braille.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/braille/braille.go deleted file mode 100644 index 7cd902f87..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/braille/braille.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Package braille provides a canvas that uses braille characters. - -This is inspired by https://github.com/asciimoo/drawille. - -The braille patterns documentation: -http://www.alanwood.net/unicode/braille_patterns.html - -The use of braille characters gives additional points (higher resolution) on -the canvas, each character cell now has eight pixels that can be set -independently. Specifically each cell has the following pixels, the axes grow -right and down. - -Each cell: - - X→ 0 1 Y - ┌───┐ ↓ - │● ●│ 0 - │● ●│ 1 - │● ●│ 2 - │● ●│ 3 - └───┘ - -When using the braille canvas, the coordinates address the sub-cell points -rather then cells themselves. However all points in the cell still share the -same cell options. -*/ -package braille - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -const ( - // ColMult is the resolution multiplier for the width, i.e. two pixels per cell. - ColMult = 2 - - // RowMult is the resolution multiplier for the height, i.e. four pixels per cell. - RowMult = 4 - - // brailleCharOffset is the offset of the braille pattern unicode characters. - // From: http://www.alanwood.net/unicode/braille_patterns.html - brailleCharOffset = 0x2800 - - // brailleLastChar is the last braille pattern rune. - brailleLastChar = 0x28FF -) - -// pixelRunes maps points addressing individual pixels in a cell into character -// offset. I.e. the correct character to set pixel(0,0) is -// brailleCharOffset|pixelRunes[image.Point{0,0}]. -var pixelRunes = map[image.Point]rune{ - {0, 0}: 0x01, {1, 0}: 0x08, - {0, 1}: 0x02, {1, 1}: 0x10, - {0, 2}: 0x04, {1, 2}: 0x20, - {0, 3}: 0x40, {1, 3}: 0x80, -} - -// Canvas is a canvas that uses the braille patterns. It is two times wider -// and four times taller than a regular canvas that uses just plain characters, -// since each cell now has 2x4 pixels that can be independently set. -// -// The braille canvas is an abstraction built on top of a regular character -// canvas. After setting and toggling pixels on the braille canvas, it should -// be copied to a regular character canvas or applied to a terminal which -// results in setting of braille pattern characters. -// See the examples for more details. -// -// The created braille canvas can be smaller and even misaligned relatively to -// the regular character canvas or terminal, allowing the callers to create a -// "view" of just a portion of the canvas or terminal. -type Canvas struct { - // regular is the regular character canvas the braille canvas is based on. - regular *canvas.Canvas -} - -// New returns a new braille canvas for the provided area. -func New(ar image.Rectangle) (*Canvas, error) { - rc, err := canvas.New(ar) - if err != nil { - return nil, err - } - return &Canvas{ - regular: rc, - }, nil -} - -// Size returns the size of the braille canvas in pixels. -func (c *Canvas) Size() image.Point { - s := c.regular.Size() - return image.Point{s.X * ColMult, s.Y * RowMult} -} - -// CellArea returns the area of the underlying cell canvas in cells. -func (c *Canvas) CellArea() image.Rectangle { - return c.regular.Area() -} - -// Area returns the area of the braille canvas in pixels. -// This will be zero-based area that is two times wider and four times taller -// than the area used to create the braille canvas. -func (c *Canvas) Area() image.Rectangle { - ar := c.regular.Area() - return image.Rect(0, 0, ar.Dx()*ColMult, ar.Dy()*RowMult) -} - -// Clear clears all the content on the canvas. -func (c *Canvas) Clear() error { - return c.regular.Clear() -} - -// SetPixel turns on pixel at the specified point. -// The provided cell options will be applied to the entire cell (all of its -// pixels). This method is idempotent. -func (c *Canvas) SetPixel(p image.Point, opts ...cell.Option) error { - cp, err := c.cellPoint(p) - if err != nil { - return err - } - cell, err := c.regular.Cell(cp) - if err != nil { - return err - } - - var r rune - if isBraille(cell.Rune) { - // If the cell already has a braille pattern rune, we will be adding - // the pixel. - r = cell.Rune - } else { - r = brailleCharOffset - } - - r |= pixelRunes[pixelPoint(p)] - if _, err := c.regular.SetCell(cp, r, opts...); err != nil { - return err - } - return nil -} - -// ClearPixel turns off pixel at the specified point. -// The provided cell options will be applied to the entire cell (all of its -// pixels). This method is idempotent. -func (c *Canvas) ClearPixel(p image.Point, opts ...cell.Option) error { - cp, err := c.cellPoint(p) - if err != nil { - return err - } - cell, err := c.regular.Cell(cp) - if err != nil { - return err - } - - // Clear is idempotent. - if !isBraille(cell.Rune) || !pixelSet(cell.Rune, p) { - return nil - } - - r := cell.Rune & ^pixelRunes[pixelPoint(p)] - if _, err := c.regular.SetCell(cp, r, opts...); err != nil { - return err - } - return nil -} - -// TogglePixel toggles the state of the pixel at the specified point, i.e. it -// either sets or clear it depending on its current state. -// The provided cell options will be applied to the entire cell (all of its -// pixels). -func (c *Canvas) TogglePixel(p image.Point, opts ...cell.Option) error { - cp, err := c.cellPoint(p) - if err != nil { - return err - } - curCell, err := c.regular.Cell(cp) - if err != nil { - return err - } - - if isBraille(curCell.Rune) && pixelSet(curCell.Rune, p) { - return c.ClearPixel(p, opts...) - } - return c.SetPixel(p, opts...) -} - -// SetCellOpts sets options on the specified cell of the braille canvas without -// modifying the content of the cell. -// Sets the default cell options if no options are provided. -// This method is idempotent. -func (c *Canvas) SetCellOpts(cellPoint image.Point, opts ...cell.Option) error { - curCell, err := c.regular.Cell(cellPoint) - if err != nil { - return err - } - - if len(opts) == 0 { - // Set the default options. - opts = []cell.Option{ - cell.FgColor(cell.ColorDefault), - cell.BgColor(cell.ColorDefault), - } - } - if _, err := c.regular.SetCell(cellPoint, curCell.Rune, opts...); err != nil { - return err - } - return nil -} - -// SetAreaCellOpts is like SetCellOpts, but sets the specified options on all -// the cells within the provided area. -func (c *Canvas) SetAreaCellOpts(cellArea image.Rectangle, opts ...cell.Option) error { - haveArea := c.regular.Area() - if !cellArea.In(haveArea) { - return fmt.Errorf("unable to set cell options in area %v, it must fit inside the available cell area is %v", cellArea, haveArea) - } - for col := cellArea.Min.X; col < cellArea.Max.X; col++ { - for row := cellArea.Min.Y; row < cellArea.Max.Y; row++ { - if err := c.SetCellOpts(image.Point{col, row}, opts...); err != nil { - return err - } - } - } - return nil -} - -// Apply applies the canvas to the corresponding area of the terminal. -// Guarantees to stay within limits of the area the canvas was created with. -func (c *Canvas) Apply(t terminalapi.Terminal) error { - return c.regular.Apply(t) -} - -// CopyTo copies the content of this canvas onto the destination canvas. -// This canvas can have an offset when compared to the destination canvas, i.e. -// the area of this canvas doesn't have to be zero-based. -func (c *Canvas) CopyTo(dst *canvas.Canvas) error { - return c.regular.CopyTo(dst) -} - -// cellPoint determines the point (coordinate) of the character cell given -// coordinates in pixels. -func (c *Canvas) cellPoint(p image.Point) (image.Point, error) { - if p.X < 0 || p.Y < 0 { - return image.ZP, fmt.Errorf("pixels cannot have negative coordinates: %v", p) - } - cp := image.Point{p.X / ColMult, p.Y / RowMult} - if ar := c.regular.Area(); !cp.In(ar) { - return image.ZP, fmt.Errorf("pixel at%v would be in a character cell at%v which falls outside of the canvas area %v", p, cp, ar) - } - return cp, nil -} - -// isBraille determines if the rune is a braille pattern rune. -func isBraille(r rune) bool { - return r >= brailleCharOffset && r <= brailleLastChar -} - -// pixelSet returns true if the provided rune has the specified pixel set. -func pixelSet(r rune, p image.Point) bool { - return r&pixelRunes[pixelPoint(p)] > 0 -} - -// pixelPoint translates point within canvas to point within the target cell. -func pixelPoint(p image.Point) image.Point { - return image.Point{p.X % ColMult, p.Y % RowMult} -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go deleted file mode 100644 index 5c21dd0ba..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/buffer/buffer.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package buffer implements a 2-D buffer of cells. -package buffer - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/area" - "github.com/mum4k/termdash/private/runewidth" -) - -// NewCells breaks the provided text into cells and applies the options. -func NewCells(text string, opts ...cell.Option) []*Cell { - var res []*Cell - for _, r := range text { - res = append(res, NewCell(r, opts...)) - } - return res -} - -// Cell represents a single cell on the terminal. -type Cell struct { - // Rune is the rune stored in the cell. - Rune rune - - // Opts are the cell options. - Opts *cell.Options -} - -// String implements fmt.Stringer. -func (c *Cell) String() string { - return fmt.Sprintf("{%q}", c.Rune) -} - -// NewCell returns a new cell. -func NewCell(r rune, opts ...cell.Option) *Cell { - return &Cell{ - Rune: r, - Opts: cell.NewOptions(opts...), - } -} - -// Copy returns a copy the cell. -func (c *Cell) Copy() *Cell { - return &Cell{ - Rune: c.Rune, - Opts: cell.NewOptions(c.Opts), - } -} - -// Apply applies the provided options to the cell. -func (c *Cell) Apply(opts ...cell.Option) { - for _, opt := range opts { - opt.Set(c.Opts) - } -} - -// Buffer is a 2-D buffer of cells. -// The axes increase right and down. -// Uninitialized buffer is invalid, use New to create an instance. -// Don't set cells directly, use the SetCell method instead which safely -// handles limits and wide unicode characters. -type Buffer [][]*Cell - -// New returns a new Buffer of the provided size. -func New(size image.Point) (Buffer, error) { - if size.X <= 0 { - return nil, fmt.Errorf("invalid buffer width (size.X): %d, must be a positive number", size.X) - } - if size.Y <= 0 { - return nil, fmt.Errorf("invalid buffer height (size.Y): %d, must be a positive number", size.Y) - } - - b := make([][]*Cell, size.X) - for col := range b { - b[col] = make([]*Cell, size.Y) - for row := range b[col] { - b[col][row] = NewCell(0) - } - } - return b, nil -} - -// SetCell sets the rune of the specified cell in the buffer. Returns the -// number of cells the rune occupies, wide runes can occupy multiple cells when -// printed on the terminal. See http://www.unicode.org/reports/tr11/. -// Use the options to specify which attributes to modify, if an attribute -// option isn't specified, the attribute retains its previous value. -func (b Buffer) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) { - partial, err := b.IsPartial(p) - if err != nil { - return -1, err - } - if partial { - return -1, fmt.Errorf("cannot set rune %q at point %v, it is a partial cell occupied by a wide rune in the previous cell", r, p) - } - - remW, err := b.RemWidth(p) - if err != nil { - return -1, err - } - rw := runewidth.RuneWidth(r) - if rw == 0 { - // Even if the rune is invisible, like the zero-value rune, it still - // occupies at least the target cell. - rw = 1 - } - if rw > remW { - return -1, fmt.Errorf("cannot set rune %q of width %d at point %v, only have %d remaining cells at this line", r, rw, p, remW) - } - - c := b[p.X][p.Y] - c.Rune = r - c.Apply(opts...) - return rw, nil -} - -// IsPartial returns true if the cell at the specified point holds a part of a -// full width rune from a previous cell. See -// http://www.unicode.org/reports/tr11/. -func (b Buffer) IsPartial(p image.Point) (bool, error) { - size := b.Size() - ar, err := area.FromSize(size) - if err != nil { - return false, err - } - - if !p.In(ar) { - return false, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar) - } - - if p.X == 0 && p.Y == 0 { - return false, nil - } - - prevP := image.Point{p.X - 1, p.Y} - if prevP.X < 0 { - prevP = image.Point{size.X - 1, p.Y - 1} - } - - prevR := b[prevP.X][prevP.Y].Rune - switch rw := runewidth.RuneWidth(prevR); rw { - case 0, 1: - return false, nil - case 2: - return true, nil - default: - return false, fmt.Errorf("buffer cell %v contains rune %q which has an unsupported rune with %d", prevP, prevR, rw) - } -} - -// RemWidth returns the remaining width (horizontal row of cells) available -// from and inclusive of the specified point. -func (b Buffer) RemWidth(p image.Point) (int, error) { - size := b.Size() - ar, err := area.FromSize(size) - if err != nil { - return -1, err - } - - if !p.In(ar) { - return -1, fmt.Errorf("point %v falls outside of the area %v occupied by the buffer", p, ar) - } - return size.X - p.X, nil -} - -// Size returns the size of the buffer. -func (b Buffer) Size() image.Point { - return image.Point{ - len(b), - len(b[0]), - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/canvas.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/canvas.go deleted file mode 100644 index 65a1e6963..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/canvas/canvas.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package canvas defines the canvas that the widgets draw on. -package canvas - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/area" - "github.com/mum4k/termdash/private/canvas/buffer" - "github.com/mum4k/termdash/private/runewidth" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// Canvas is where a widget draws its output for display on the terminal. -type Canvas struct { - // area is the area the buffer was created for. - // Contains absolute coordinates on the target terminal, while the buffer - // contains relative zero-based coordinates for this canvas. - area image.Rectangle - - // buffer is where the drawing happens. - buffer buffer.Buffer -} - -// New returns a new Canvas with a buffer for the provided area. -func New(ar image.Rectangle) (*Canvas, error) { - if ar.Min.X < 0 || ar.Min.Y < 0 || ar.Max.X < 0 || ar.Max.Y < 0 { - return nil, fmt.Errorf("area cannot start or end on the negative axis, got: %+v", ar) - } - - b, err := buffer.New(area.Size(ar)) - if err != nil { - return nil, err - } - return &Canvas{ - area: ar, - buffer: b, - }, nil -} - -// Size returns the size of the 2-D canvas. -func (c *Canvas) Size() image.Point { - return c.buffer.Size() -} - -// Area returns the area of the 2-D canvas. -func (c *Canvas) Area() image.Rectangle { - s := c.buffer.Size() - return image.Rect(0, 0, s.X, s.Y) -} - -// Clear clears all the content on the canvas. -func (c *Canvas) Clear() error { - b, err := buffer.New(c.Size()) - if err != nil { - return err - } - c.buffer = b - return nil -} - -// SetCell sets the rune of the specified cell on the canvas. Returns the -// number of cells the rune occupies, wide runes can occupy multiple cells when -// printed on the terminal. See http://www.unicode.org/reports/tr11/. -// Use the options to specify which attributes to modify, if an attribute -// option isn't specified, the attribute retains its previous value. -func (c *Canvas) SetCell(p image.Point, r rune, opts ...cell.Option) (int, error) { - return c.buffer.SetCell(p, r, opts...) -} - -// Cell returns a copy of the specified cell. -func (c *Canvas) Cell(p image.Point) (*buffer.Cell, error) { - ar, err := area.FromSize(c.Size()) - if err != nil { - return nil, err - } - if !p.In(ar) { - return nil, fmt.Errorf("point %v falls outside of the area %v occupied by the canvas", p, ar) - } - - return c.buffer[p.X][p.Y].Copy(), nil -} - -// SetCellOpts sets options on the specified cell of the canvas without -// modifying the content of the cell. -// Sets the default cell options if no options are provided. -// This method is idempotent. -func (c *Canvas) SetCellOpts(p image.Point, opts ...cell.Option) error { - curCell, err := c.Cell(p) - if err != nil { - return err - } - - if len(opts) == 0 { - // Set the default options. - opts = []cell.Option{ - cell.FgColor(cell.ColorDefault), - cell.BgColor(cell.ColorDefault), - } - } - if _, err := c.SetCell(p, curCell.Rune, opts...); err != nil { - return err - } - return nil -} - -// SetAreaCells is like SetCell, but sets the specified rune and options on all -// the cells within the provided area. -// This method is idempotent. -func (c *Canvas) SetAreaCells(cellArea image.Rectangle, r rune, opts ...cell.Option) error { - haveArea := c.Area() - if !cellArea.In(haveArea) { - return fmt.Errorf("unable to set cell runes in area %v, it must fit inside the available cell area is %v", cellArea, haveArea) - } - - rw := runewidth.RuneWidth(r) - for row := cellArea.Min.Y; row < cellArea.Max.Y; row++ { - for col := cellArea.Min.X; col < cellArea.Max.X; { - p := image.Point{col, row} - if col+rw > cellArea.Max.X { - break - } - cells, err := c.SetCell(p, r, opts...) - if err != nil { - return err - } - col += cells - } - } - return nil -} - -// SetAreaCellOpts is like SetCellOpts, but sets the specified options on all -// the cells within the provided area. -func (c *Canvas) SetAreaCellOpts(cellArea image.Rectangle, opts ...cell.Option) error { - haveArea := c.Area() - if !cellArea.In(haveArea) { - return fmt.Errorf("unable to set cell options in area %v, it must fit inside the available cell area is %v", cellArea, haveArea) - } - for col := cellArea.Min.X; col < cellArea.Max.X; col++ { - for row := cellArea.Min.Y; row < cellArea.Max.Y; row++ { - if err := c.SetCellOpts(image.Point{col, row}, opts...); err != nil { - return err - } - } - } - return nil -} - -// setCellFunc is a function that sets cell content on a terminal or a canvas. -type setCellFunc func(image.Point, rune, ...cell.Option) error - -// copyTo is the internal implementation of code that copies the content of a -// canvas. If a non zero offset is provided, all the copied points are offset by -// this amount. -// The dstSetCell function is called for every point in this canvas when -// copying it to the destination. -func (c *Canvas) copyTo(offset image.Point, dstSetCell setCellFunc) error { - for col := range c.buffer { - for row := range c.buffer[col] { - partial, err := c.buffer.IsPartial(image.Point{col, row}) - if err != nil { - return err - } - if partial { - // Skip over partial cells, i.e. cells that follow a cell - // containing a full-width rune. A full-width rune takes only - // one cell in the buffer, but two on the terminal. - // See http://www.unicode.org/reports/tr11/. - continue - } - cell := c.buffer[col][row] - p := image.Point{col, row}.Add(offset) - if err := dstSetCell(p, cell.Rune, cell.Opts); err != nil { - return fmt.Errorf("setCellFunc%v => error: %v", p, err) - } - } - } - return nil -} - -// Apply applies the canvas to the corresponding area of the terminal. -// Guarantees to stay within limits of the area the canvas was created with. -func (c *Canvas) Apply(t terminalapi.Terminal) error { - termArea, err := area.FromSize(t.Size()) - if err != nil { - return err - } - - bufArea, err := area.FromSize(c.buffer.Size()) - if err != nil { - return err - } - - if !bufArea.In(termArea) { - return fmt.Errorf("the canvas area %+v doesn't fit onto the terminal %+v", bufArea, termArea) - } - - // The image.Point{0, 0} of this canvas isn't always exactly at - // image.Point{0, 0} on the terminal. - // Depends on area assigned by the container. - offset := c.area.Min - return c.copyTo(offset, t.SetCell) -} - -// CopyTo copies the content of this canvas onto the destination canvas. -// This canvas can have an offset when compared to the destination canvas, i.e. -// the area of this canvas doesn't have to be zero-based. -func (c *Canvas) CopyTo(dst *Canvas) error { - if !c.area.In(dst.Area()) { - return fmt.Errorf("the canvas area %v doesn't fit or lie inside the destination canvas area %v", c.area, dst.Area()) - } - - fn := setCellFunc(func(p image.Point, r rune, opts ...cell.Option) error { - if _, err := dst.SetCell(p, r, opts...); err != nil { - return fmt.Errorf("dst.SetCell => %v", err) - } - return nil - }) - - // Neither of the two canvases (source and destination) have to be zero - // based. Canvas is not zero based if it is positioned elsewhere, i.e. - // providing a smaller view of another canvas. - // E.g. a widget can assign a smaller portion of its canvas to a component - // in order to restrict drawing of this component to a smaller area. To do - // this it can create a sub-canvas. This sub-canvas can have a specific - // starting position other than image.Point{0, 0} relative to the parent - // canvas. Copying this sub-canvas back onto the parent accounts for this - // offset. - offset := c.area.Min - return c.copyTo(offset, fn) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/border.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/border.go deleted file mode 100644 index a19ec096c..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/border.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// border.go contains code that draws borders. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/align" - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/linestyle" - "github.com/mum4k/termdash/private/alignfor" - "github.com/mum4k/termdash/private/canvas" -) - -// BorderOption is used to provide options to Border(). -type BorderOption interface { - // set sets the provided option. - set(*borderOptions) -} - -// borderOptions stores the provided options. -type borderOptions struct { - cellOpts []cell.Option - lineStyle linestyle.LineStyle - title string - titleOM OverrunMode - titleCellOpts []cell.Option - titleHAlign align.Horizontal -} - -// borderOption implements BorderOption. -type borderOption func(bOpts *borderOptions) - -// set implements BorderOption.set. -func (bo borderOption) set(bOpts *borderOptions) { - bo(bOpts) -} - -// DefaultBorderLineStyle is the default value for the BorderLineStyle option. -const DefaultBorderLineStyle = linestyle.Light - -// BorderLineStyle sets the style of the line used to draw the border. -func BorderLineStyle(ls linestyle.LineStyle) BorderOption { - return borderOption(func(bOpts *borderOptions) { - bOpts.lineStyle = ls - }) -} - -// BorderCellOpts sets options on the cells that create the border. -func BorderCellOpts(opts ...cell.Option) BorderOption { - return borderOption(func(bOpts *borderOptions) { - bOpts.cellOpts = opts - }) -} - -// BorderTitle sets a title for the border. -func BorderTitle(title string, overrun OverrunMode, opts ...cell.Option) BorderOption { - return borderOption(func(bOpts *borderOptions) { - bOpts.title = title - bOpts.titleOM = overrun - bOpts.titleCellOpts = opts - }) -} - -// BorderTitleAlign configures the horizontal alignment for the title. -func BorderTitleAlign(h align.Horizontal) BorderOption { - return borderOption(func(bOpts *borderOptions) { - bOpts.titleHAlign = h - }) -} - -// borderChar returns the correct border character from the parts for the use -// at the specified point of the border. Returns -1 if no character should be at -// this point. -func borderChar(p image.Point, border image.Rectangle, parts map[linePart]rune) rune { - switch { - case p.X == border.Min.X && p.Y == border.Min.Y: - return parts[topLeftCorner] - case p.X == border.Max.X-1 && p.Y == border.Min.Y: - return parts[topRightCorner] - case p.X == border.Min.X && p.Y == border.Max.Y-1: - return parts[bottomLeftCorner] - case p.X == border.Max.X-1 && p.Y == border.Max.Y-1: - return parts[bottomRightCorner] - case p.X == border.Min.X || p.X == border.Max.X-1: - return parts[vLine] - case p.Y == border.Min.Y || p.Y == border.Max.Y-1: - return parts[hLine] - } - return -1 -} - -// drawTitle draws a text title at the top of the border. -func drawTitle(c *canvas.Canvas, border image.Rectangle, opt *borderOptions) error { - // Don't attempt to draw the title if there isn't space for at least one rune. - // The title must not overwrite any of the corner runes on the border so we - // need the following minimum width. - const minForTitle = 3 - if border.Dx() < minForTitle { - return nil - } - - available := image.Rect( - border.Min.X+1, // One space for the top left corner char. - border.Min.Y, - border.Max.X-1, // One space for the top right corner char. - border.Min.Y+1, - ) - start, err := alignfor.Text(available, opt.title, opt.titleHAlign, align.VerticalTop) - if err != nil { - return err - } - - return Text( - c, opt.title, start, - TextCellOpts(opt.titleCellOpts...), - TextOverrunMode(opt.titleOM), - TextMaxX(available.Max.X), - ) -} - -// Border draws a border on the canvas. -func Border(c *canvas.Canvas, border image.Rectangle, opts ...BorderOption) error { - if ar := c.Area(); !border.In(ar) { - return fmt.Errorf("the requested border %+v falls outside of the provided canvas %+v", border, ar) - } - - const minSize = 2 - if border.Dx() < minSize || border.Dy() < minSize { - return fmt.Errorf("the smallest supported border is %dx%d, got: %dx%d", minSize, minSize, border.Dx(), border.Dy()) - } - - opt := &borderOptions{ - lineStyle: DefaultBorderLineStyle, - } - for _, o := range opts { - o.set(opt) - } - - parts, err := lineParts(opt.lineStyle) - if err != nil { - return err - } - - for col := border.Min.X; col < border.Max.X; col++ { - for row := border.Min.Y; row < border.Max.Y; row++ { - p := image.Point{col, row} - r := borderChar(p, border, parts) - if r == -1 { - continue - } - - cells, err := c.SetCell(p, r, opt.cellOpts...) - if err != nil { - return err - } - if cells != 1 { - panic(fmt.Sprintf("invalid border rune %q, this rune occupies %d cells, border implementation only supports half-width runes that occupy exactly one cell", r, cells)) - } - } - } - - if opt.title != "" { - return drawTitle(c, border, opt) - } - return nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_circle.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_circle.go deleted file mode 100644 index d2b3b86bc..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_circle.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// braille_circle.go contains code that draws circles on a braille canvas. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas/braille" - "github.com/mum4k/termdash/private/numbers/trig" -) - -// BrailleCircleOption is used to provide options to BrailleCircle. -type BrailleCircleOption interface { - // set sets the provided option. - set(*brailleCircleOptions) -} - -// brailleCircleOptions stores the provided options. -type brailleCircleOptions struct { - cellOpts []cell.Option - filled bool - pixelChange braillePixelChange - - arcOnly bool - startDegree int - endDegree int -} - -// newBrailleCircleOptions returns a new brailleCircleOptions instance. -func newBrailleCircleOptions() *brailleCircleOptions { - return &brailleCircleOptions{ - pixelChange: braillePixelChangeSet, - } -} - -// validate validates the provided options. -func (opts *brailleCircleOptions) validate() error { - if !opts.arcOnly { - return nil - } - - if opts.startDegree == opts.endDegree { - return fmt.Errorf("invalid degree range, start %d and end %d cannot be equal", opts.startDegree, opts.endDegree) - } - return nil -} - -// brailleCircleOption implements BrailleCircleOption. -type brailleCircleOption func(*brailleCircleOptions) - -// set implements BrailleCircleOption.set. -func (o brailleCircleOption) set(opts *brailleCircleOptions) { - o(opts) -} - -// BrailleCircleCellOpts sets options on the cells that contain the circle. -// Cell options on a braille canvas can only be set on the entire cell, not per -// pixel. -func BrailleCircleCellOpts(cOpts ...cell.Option) BrailleCircleOption { - return brailleCircleOption(func(opts *brailleCircleOptions) { - opts.cellOpts = cOpts - }) -} - -// BrailleCircleFilled indicates that the drawn circle should be filled. -func BrailleCircleFilled() BrailleCircleOption { - return brailleCircleOption(func(opts *brailleCircleOptions) { - opts.filled = true - }) -} - -// BrailleCircleArcOnly indicates that only a portion of the circle should be drawn. -// The arc will be between the two provided angles in degrees. -// Each angle must be in range 0 <= angle <= 360. Start and end must not be equal. -// The zero angle is on the X axis, angles grow counter-clockwise. -func BrailleCircleArcOnly(startDegree, endDegree int) BrailleCircleOption { - return brailleCircleOption(func(opts *brailleCircleOptions) { - opts.arcOnly = true - opts.startDegree = startDegree - opts.endDegree = endDegree - - }) -} - -// BrailleCircleClearPixels changes the behavior of BrailleCircle, so that it -// clears the pixels belonging to the circle instead of setting them. -// Useful in order to "erase" a circle from the canvas as opposed to drawing one. -func BrailleCircleClearPixels() BrailleCircleOption { - return brailleCircleOption(func(opts *brailleCircleOptions) { - opts.pixelChange = braillePixelChangeClear - }) -} - -// BrailleCircle draws an approximated circle with the specified mid point and radius. -// The mid point must be a valid pixel within the canvas. -// All the points that form the circle must fit into the canvas. -// The smallest valid radius is two. -func BrailleCircle(bc *braille.Canvas, mid image.Point, radius int, opts ...BrailleCircleOption) error { - if ar := bc.Area(); !mid.In(ar) { - return fmt.Errorf("unable to draw circle with mid point %v which is outside of the braille canvas area %v", mid, ar) - } - if min := 2; radius < min { - return fmt.Errorf("unable to draw circle with radius %d, must be in range %d <= radius", radius, min) - } - - opt := newBrailleCircleOptions() - for _, o := range opts { - o.set(opt) - } - - if err := opt.validate(); err != nil { - return err - } - - points := circlePoints(mid, radius) - if opt.arcOnly { - f, err := trig.FilterByAngle(points, mid, opt.startDegree, opt.endDegree) - if err != nil { - return err - } - points = f - if opt.filled && (opt.startDegree != 0 || opt.endDegree != 360) { - points = append(points, openingPoints(mid, radius, opt)...) - } - } - if err := drawPoints(bc, points, opt); err != nil { - return fmt.Errorf("failed to draw circle with mid:%v, radius:%d, start:%d degrees, end:%d degrees: %v", mid, radius, opt.startDegree, opt.endDegree, err) - } - if opt.filled { - return fillCircle(bc, points, mid, radius, opt) - } - return nil -} - -// drawPoints draws the points onto the canvas. -func drawPoints(bc *braille.Canvas, points []image.Point, opt *brailleCircleOptions) error { - for _, p := range points { - switch opt.pixelChange { - case braillePixelChangeSet: - if err := bc.SetPixel(p, opt.cellOpts...); err != nil { - return fmt.Errorf("SetPixel => %v", err) - } - case braillePixelChangeClear: - if err := bc.ClearPixel(p, opt.cellOpts...); err != nil { - return fmt.Errorf("ClearPixel => %v", err) - } - - } - } - return nil -} - -// fillCircle fills a circle that consists of the provided point and has the -// mid point and radius. -func fillCircle(bc *braille.Canvas, points []image.Point, mid image.Point, radius int, opt *brailleCircleOptions) error { - lineOpts := []BrailleLineOption{ - BrailleLineCellOpts(opt.cellOpts...), - } - fillOpts := []BrailleFillOption{ - BrailleFillCellOpts(opt.cellOpts...), - } - if opt.pixelChange == braillePixelChangeClear { - lineOpts = append(lineOpts, BrailleLineClearPixels()) - fillOpts = append(fillOpts, BrailleFillClearPixels()) - } - - // Determine a fill point that should be inside of the circle sector. - midA, err := trig.RangeMid(opt.startDegree, opt.endDegree) - if err != nil { - return err - } - fp := trig.CirclePointAtAngle(midA, mid, radius-1) - - // Ensure the fill point falls inside the circle. - // If drawing a partial circle, it must also fall within points belonging - // to the opening. - // This might not be true if drawing a partial circle and the arc is very - // small. - shape := points - if opt.arcOnly { - startP := trig.CirclePointAtAngle(opt.startDegree, mid, radius-1) - endP := trig.CirclePointAtAngle(opt.endDegree, mid, radius-1) - shape = append(shape, startP, endP) - } - if trig.PointIsIn(fp, shape) { - if err := BrailleFill(bc, fp, points, fillOpts...); err != nil { - return err - } - if err := BrailleLine(bc, mid, fp, lineOpts...); err != nil { - return err - } - } - return nil -} - -// openingPoints returns points on the lines from the mid point to the circle -// opening when drawing an incomplete circle. -func openingPoints(mid image.Point, radius int, opt *brailleCircleOptions) []image.Point { - var points []image.Point - startP := trig.CirclePointAtAngle(opt.startDegree, mid, radius) - endP := trig.CirclePointAtAngle(opt.endDegree, mid, radius) - points = append(points, brailleLinePoints(mid, startP)...) - points = append(points, brailleLinePoints(mid, endP)...) - return points -} - -// circlePoints returns a list of points that represent a circle with -// the specified mid point and radius. -func circlePoints(mid image.Point, radius int) []image.Point { - var points []image.Point - - // Bresenham algorithm. - // https://en.wikipedia.org/wiki/Midpoint_circle_algorithm - x := radius - y := 0 - dx := 1 - dy := 1 - diff := dx - (radius << 1) // Cheap multiplication by two. - - for x >= y { - points = append( - points, - image.Point{mid.X + x, mid.Y + y}, - image.Point{mid.X + y, mid.Y + x}, - image.Point{mid.X - y, mid.Y + x}, - image.Point{mid.X - x, mid.Y + y}, - image.Point{mid.X - x, mid.Y - y}, - image.Point{mid.X - y, mid.Y - x}, - image.Point{mid.X + y, mid.Y - x}, - image.Point{mid.X + x, mid.Y - y}, - ) - - if diff <= 0 { - y++ - diff += dy - dy += 2 - } - - if diff > 0 { - x-- - dx += 2 - diff += dx - (radius << 1) - } - - } - return points -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_fill.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_fill.go deleted file mode 100644 index 8bb311f1c..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_fill.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// braille_fill.go implements the flood-fill algorithm for filling shapes on the braille canvas. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas/braille" -) - -// BrailleFillOption is used to provide options to BrailleFill. -type BrailleFillOption interface { - // set sets the provided option. - set(*brailleFillOptions) -} - -// brailleFillOptions stores the provided options. -type brailleFillOptions struct { - cellOpts []cell.Option - pixelChange braillePixelChange -} - -// newBrailleFillOptions returns a new brailleFillOptions instance. -func newBrailleFillOptions() *brailleFillOptions { - return &brailleFillOptions{ - pixelChange: braillePixelChangeSet, - } -} - -// brailleFillOption implements BrailleFillOption. -type brailleFillOption func(*brailleFillOptions) - -// set implements BrailleFillOption.set. -func (o brailleFillOption) set(opts *brailleFillOptions) { - o(opts) -} - -// BrailleFillCellOpts sets options on the cells that are set as part of -// filling shapes. -// Cell options on a braille canvas can only be set on the entire cell, not per -// pixel. -func BrailleFillCellOpts(cOpts ...cell.Option) BrailleFillOption { - return brailleFillOption(func(opts *brailleFillOptions) { - opts.cellOpts = cOpts - }) -} - -// BrailleFillClearPixels changes the behavior of BrailleFill, so that it -// clears the pixels instead of setting them. -// Useful in order to "erase" the filled area as opposed to drawing one. -func BrailleFillClearPixels() BrailleFillOption { - return brailleFillOption(func(opts *brailleFillOptions) { - opts.pixelChange = braillePixelChangeClear - }) -} - -// BrailleFill fills the braille canvas starting at the specified point. -// The function will not fill or cross over any points in the defined border. -// The start point must be in the canvas. -func BrailleFill(bc *braille.Canvas, start image.Point, border []image.Point, opts ...BrailleFillOption) error { - if ar := bc.Area(); !start.In(ar) { - return fmt.Errorf("unable to start filling canvas at point %v which is outside of the braille canvas area %v", start, ar) - } - - opt := newBrailleFillOptions() - for _, o := range opts { - o.set(opt) - } - - b := map[image.Point]struct{}{} - for _, p := range border { - b[p] = struct{}{} - } - - v := newVisitable(bc.Area(), b) - visitor := func(p image.Point) error { - switch opt.pixelChange { - case braillePixelChangeSet: - return bc.SetPixel(p, opt.cellOpts...) - case braillePixelChangeClear: - return bc.ClearPixel(p, opt.cellOpts...) - } - return nil - } - return brailleDFS(v, start, visitor) -} - -// visitable represents an area that can be visited. -// It tracks nodes that are already visited. -type visitable struct { - area image.Rectangle - visited map[image.Point]struct{} -} - -// newVisitable returns a new visitable object initialized for the provided -// area and already visited nodes. -func newVisitable(ar image.Rectangle, visited map[image.Point]struct{}) *visitable { - if visited == nil { - visited = map[image.Point]struct{}{} - } - return &visitable{ - area: ar, - visited: visited, - } -} - -// neighborsAt returns all valid neighbors for the specified point. -func (v *visitable) neighborsAt(p image.Point) []image.Point { - var res []image.Point - for _, neigh := range []image.Point{ - {p.X - 1, p.Y}, // left - {p.X + 1, p.Y}, // right - {p.X, p.Y - 1}, // up - {p.X, p.Y + 1}, // down - } { - if !neigh.In(v.area) { - continue - } - if _, ok := v.visited[neigh]; ok { - continue - } - v.visited[neigh] = struct{}{} - res = append(res, neigh) - } - return res -} - -// brailleDFS visits every point in the area and runs the visitor function. -func brailleDFS(v *visitable, p image.Point, visitFn func(image.Point) error) error { - neigh := v.neighborsAt(p) - if len(neigh) == 0 { - return nil - } - - for _, n := range neigh { - if err := visitFn(n); err != nil { - return err - } - if err := brailleDFS(v, n, visitFn); err != nil { - return err - } - } - return nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_line.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_line.go deleted file mode 100644 index c9f412321..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/braille_line.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// braille_line.go contains code that draws lines on a braille canvas. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas/braille" - "github.com/mum4k/termdash/private/numbers" -) - -// braillePixelChange represents an action on a pixel on the braille canvas. -type braillePixelChange int - -// String implements fmt.Stringer() -func (bpc braillePixelChange) String() string { - if n, ok := braillePixelChangeNames[bpc]; ok { - return n - } - return "braillePixelChangeUnknown" -} - -// braillePixelChangeNames maps braillePixelChange values to human readable names. -var braillePixelChangeNames = map[braillePixelChange]string{ - braillePixelChangeSet: "braillePixelChangeSet", - braillePixelChangeClear: "braillePixelChangeClear", -} - -const ( - braillePixelChangeUnknown braillePixelChange = iota - - braillePixelChangeSet - braillePixelChangeClear -) - -// BrailleLineOption is used to provide options to BrailleLine(). -type BrailleLineOption interface { - // set sets the provided option. - set(*brailleLineOptions) -} - -// brailleLineOptions stores the provided options. -type brailleLineOptions struct { - cellOpts []cell.Option - pixelChange braillePixelChange -} - -// newBrailleLineOptions returns a new brailleLineOptions instance. -func newBrailleLineOptions() *brailleLineOptions { - return &brailleLineOptions{ - pixelChange: braillePixelChangeSet, - } -} - -// brailleLineOption implements BrailleLineOption. -type brailleLineOption func(*brailleLineOptions) - -// set implements BrailleLineOption.set. -func (o brailleLineOption) set(opts *brailleLineOptions) { - o(opts) -} - -// BrailleLineCellOpts sets options on the cells that contain the line. -// Cell options on a braille canvas can only be set on the entire cell, not per -// pixel. -func BrailleLineCellOpts(cOpts ...cell.Option) BrailleLineOption { - return brailleLineOption(func(opts *brailleLineOptions) { - opts.cellOpts = cOpts - }) -} - -// BrailleLineClearPixels changes the behavior of BrailleLine, so that it -// clears the pixels belonging to the line instead of setting them. -// Useful in order to "erase" a line from the canvas as opposed to drawing one. -func BrailleLineClearPixels() BrailleLineOption { - return brailleLineOption(func(opts *brailleLineOptions) { - opts.pixelChange = braillePixelChangeClear - }) -} - -// BrailleLine draws an approximated line segment on the braille canvas between -// the two provided points. -// Both start and end must be valid points within the canvas. Start and end can -// be the same point in which case only one pixel will be set on the braille -// canvas. -// The start or end coordinates must not be negative. -func BrailleLine(bc *braille.Canvas, start, end image.Point, opts ...BrailleLineOption) error { - if start.X < 0 || start.Y < 0 { - return fmt.Errorf("the start coordinates cannot be negative, got: %v", start) - } - if end.X < 0 || end.Y < 0 { - return fmt.Errorf("the end coordinates cannot be negative, got: %v", end) - } - - opt := newBrailleLineOptions() - for _, o := range opts { - o.set(opt) - } - - points := brailleLinePoints(start, end) - for _, p := range points { - switch opt.pixelChange { - case braillePixelChangeSet: - if err := bc.SetPixel(p, opt.cellOpts...); err != nil { - return fmt.Errorf("bc.SetPixel(%v) => %v", p, err) - } - case braillePixelChangeClear: - if err := bc.ClearPixel(p, opt.cellOpts...); err != nil { - return fmt.Errorf("bc.ClearPixel(%v) => %v", p, err) - } - } - } - return nil -} - -// brailleLinePoints returns the points to set when drawing the line. -func brailleLinePoints(start, end image.Point) []image.Point { - // Implements Bresenham's line algorithm. - // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm - - vertProj := numbers.Abs(end.Y - start.Y) - horizProj := numbers.Abs(end.X - start.X) - if vertProj < horizProj { - if start.X > end.X { - return lineLow(end.X, end.Y, start.X, start.Y) - } - return lineLow(start.X, start.Y, end.X, end.Y) - } - if start.Y > end.Y { - return lineHigh(end.X, end.Y, start.X, start.Y) - } - return lineHigh(start.X, start.Y, end.X, end.Y) -} - -// lineLow returns points that create a line whose horizontal projection -// (end.X - start.X) is longer than its vertical projection -// (end.Y - start.Y). -func lineLow(x0, y0, x1, y1 int) []image.Point { - deltaX := x1 - x0 - deltaY := y1 - y0 - - stepY := 1 - if deltaY < 0 { - stepY = -1 - deltaY = -deltaY - } - - var res []image.Point - diff := 2*deltaY - deltaX - y := y0 - for x := x0; x <= x1; x++ { - res = append(res, image.Point{x, y}) - if diff > 0 { - y += stepY - diff -= 2 * deltaX - } - diff += 2 * deltaY - } - return res -} - -// lineHigh returns points that createa line whose vertical projection -// (end.Y - start.Y) is longer than its horizontal projection -// (end.X - start.X). -func lineHigh(x0, y0, x1, y1 int) []image.Point { - deltaX := x1 - x0 - deltaY := y1 - y0 - - stepX := 1 - if deltaX < 0 { - stepX = -1 - deltaX = -deltaX - } - - var res []image.Point - diff := 2*deltaX - deltaY - x := x0 - for y := y0; y <= y1; y++ { - res = append(res, image.Point{x, y}) - - if diff > 0 { - x += stepX - diff -= 2 * deltaY - } - diff += 2 * deltaX - } - return res -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/draw.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/draw.go deleted file mode 100644 index 37c01bf7e..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/draw.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package draw provides functions that draw lines, shapes, etc on 2-D terminal -// like canvases. -package draw diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/hv_line.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/hv_line.go deleted file mode 100644 index 35318f42d..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/hv_line.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// hv_line.go contains code that draws horizontal and vertical lines. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/linestyle" - "github.com/mum4k/termdash/private/canvas" -) - -// HVLineOption is used to provide options to HVLine(). -type HVLineOption interface { - // set sets the provided option. - set(*hVLineOptions) -} - -// hVLineOptions stores the provided options. -type hVLineOptions struct { - cellOpts []cell.Option - lineStyle linestyle.LineStyle -} - -// newHVLineOptions returns a new hVLineOptions instance. -func newHVLineOptions() *hVLineOptions { - return &hVLineOptions{ - lineStyle: DefaultLineStyle, - } -} - -// hVLineOption implements HVLineOption. -type hVLineOption func(*hVLineOptions) - -// set implements HVLineOption.set. -func (o hVLineOption) set(opts *hVLineOptions) { - o(opts) -} - -// DefaultLineStyle is the default value for the HVLineStyle option. -const DefaultLineStyle = linestyle.Light - -// HVLineStyle sets the style of the line. -// Defaults to DefaultLineStyle. -func HVLineStyle(ls linestyle.LineStyle) HVLineOption { - return hVLineOption(func(opts *hVLineOptions) { - opts.lineStyle = ls - }) -} - -// HVLineCellOpts sets options on the cells that contain the line. -func HVLineCellOpts(cOpts ...cell.Option) HVLineOption { - return hVLineOption(func(opts *hVLineOptions) { - opts.cellOpts = cOpts - }) -} - -// HVLine represents one horizontal or vertical line. -type HVLine struct { - // Start is the cell where the line starts. - Start image.Point - // End is the cell where the line ends. - End image.Point -} - -// HVLines draws horizontal or vertical lines. Handles drawing of the correct -// characters for locations where any two lines cross (e.g. a corner, a T shape -// or a cross). Each line must be at least two cells long. Both start and end -// must be on the same horizontal (same X coordinate) or same vertical (same Y -// coordinate) line. -func HVLines(c *canvas.Canvas, lines []HVLine, opts ...HVLineOption) error { - opt := newHVLineOptions() - for _, o := range opts { - o.set(opt) - } - - g := newHVLineGraph() - for _, l := range lines { - line, err := newHVLine(c, l.Start, l.End, opt) - if err != nil { - return err - } - g.addLine(line) - - switch { - case line.horizontal(): - for curX := line.start.X; ; curX++ { - cur := image.Point{curX, line.start.Y} - if _, err := c.SetCell(cur, line.mainPart, opt.cellOpts...); err != nil { - return err - } - - if curX == line.end.X { - break - } - } - - case line.vertical(): - for curY := line.start.Y; ; curY++ { - cur := image.Point{line.start.X, curY} - if _, err := c.SetCell(cur, line.mainPart, opt.cellOpts...); err != nil { - return err - } - - if curY == line.end.Y { - break - } - } - } - } - - for _, n := range g.multiEdgeNodes() { - r, err := n.rune(opt.lineStyle) - if err != nil { - return err - } - if _, err := c.SetCell(n.p, r, opt.cellOpts...); err != nil { - return err - } - } - - return nil -} - -// hVLine represents a line that will be drawn on the canvas. -type hVLine struct { - // start is the starting point of the line. - start image.Point - - // end is the ending point of the line. - end image.Point - - // mainPart is either parts[vLine] or parts[hLine] depending on whether - // this is horizontal or vertical line. - mainPart rune - - // opts are the options provided in a call to HVLine(). - opts *hVLineOptions -} - -// newHVLine creates a new hVLine instance. -// Swaps start and end if necessary, so that horizontal drawing is always left -// to right and vertical is always top down. -func newHVLine(c *canvas.Canvas, start, end image.Point, opts *hVLineOptions) (*hVLine, error) { - if ar := c.Area(); !start.In(ar) || !end.In(ar) { - return nil, fmt.Errorf("both the start%v and the end%v must be in the canvas area: %v", start, end, ar) - } - - parts, err := lineParts(opts.lineStyle) - if err != nil { - return nil, err - } - - var mainPart rune - switch { - case start.X != end.X && start.Y != end.Y: - return nil, fmt.Errorf("can only draw horizontal (same X coordinates) or vertical (same Y coordinates), got start:%v end:%v", start, end) - - case start.X == end.X && start.Y == end.Y: - return nil, fmt.Errorf("the line must at least one cell long, got start%v, end%v", start, end) - - case start.X == end.X: - mainPart = parts[vLine] - if start.Y > end.Y { - start, end = end, start - } - - case start.Y == end.Y: - mainPart = parts[hLine] - if start.X > end.X { - start, end = end, start - } - - } - - return &hVLine{ - start: start, - end: end, - mainPart: mainPart, - opts: opts, - }, nil -} - -// horizontal determines if this is a horizontal line. -func (hvl *hVLine) horizontal() bool { - return hvl.mainPart == lineStyleChars[hvl.opts.lineStyle][hLine] -} - -// vertical determines if this is a vertical line. -func (hvl *hVLine) vertical() bool { - return hvl.mainPart == lineStyleChars[hvl.opts.lineStyle][vLine] -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/hv_line_graph.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/hv_line_graph.go deleted file mode 100644 index ccbc72a57..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/hv_line_graph.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// hv_line_graph.go helps to keep track of locations where lines cross. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/linestyle" -) - -// hVLineEdge is an edge between two points on the graph. -type hVLineEdge struct { - // from is the starting node of this edge. - // From is guaranteed to be less than to. - from image.Point - - // to is the ending point of this edge. - to image.Point -} - -// newHVLineEdge returns a new edge between the two points. -func newHVLineEdge(from, to image.Point) hVLineEdge { - return hVLineEdge{ - from: from, - to: to, - } -} - -// hVLineNode represents one node in the graph. -// I.e. one cell. -type hVLineNode struct { - // p is the point where this node is. - p image.Point - - // edges are the edges between this node and the surrounding nodes. - // The code only supports horizontal and vertical lines so there can only - // ever be edges to nodes on these planes. - edges map[hVLineEdge]bool -} - -// newHVLineNode creates a new newHVLineNode. -func newHVLineNode(p image.Point) *hVLineNode { - return &hVLineNode{ - p: p, - edges: map[hVLineEdge]bool{}, - } -} - -// hasDown determines if this node has an edge to the one below it. -func (n *hVLineNode) hasDown() bool { - target := newHVLineEdge(n.p, image.Point{n.p.X, n.p.Y + 1}) - _, ok := n.edges[target] - return ok -} - -// hasUp determines if this node has an edge to the one above it. -func (n *hVLineNode) hasUp() bool { - target := newHVLineEdge(image.Point{n.p.X, n.p.Y - 1}, n.p) - _, ok := n.edges[target] - return ok -} - -// hasLeft determines if this node has an edge to the next node on the left. -func (n *hVLineNode) hasLeft() bool { - target := newHVLineEdge(image.Point{n.p.X - 1, n.p.Y}, n.p) - _, ok := n.edges[target] - return ok -} - -// hasRight determines if this node has an edge to the next node on the right. -func (n *hVLineNode) hasRight() bool { - target := newHVLineEdge(n.p, image.Point{n.p.X + 1, n.p.Y}) - _, ok := n.edges[target] - return ok -} - -// rune, given the selected line style returns the correct line character to -// represent this node. -// Only handles nodes with two or more edges, as returned by multiEdgeNodes(). -func (n *hVLineNode) rune(ls linestyle.LineStyle) (rune, error) { - parts, err := lineParts(ls) - if err != nil { - return -1, err - } - - switch len(n.edges) { - case 2: - switch { - case n.hasLeft() && n.hasRight(): - return parts[hLine], nil - case n.hasUp() && n.hasDown(): - return parts[vLine], nil - case n.hasDown() && n.hasRight(): - return parts[topLeftCorner], nil - case n.hasDown() && n.hasLeft(): - return parts[topRightCorner], nil - case n.hasUp() && n.hasRight(): - return parts[bottomLeftCorner], nil - case n.hasUp() && n.hasLeft(): - return parts[bottomRightCorner], nil - default: - return -1, fmt.Errorf("unexpected two edges in node representing point %v: %v", n.p, n.edges) - } - - case 3: - switch { - case n.hasUp() && n.hasLeft() && n.hasRight(): - return parts[hAndUp], nil - case n.hasDown() && n.hasLeft() && n.hasRight(): - return parts[hAndDown], nil - case n.hasUp() && n.hasDown() && n.hasRight(): - return parts[vAndRight], nil - case n.hasUp() && n.hasDown() && n.hasLeft(): - return parts[vAndLeft], nil - - default: - return -1, fmt.Errorf("unexpected three edges in node representing point %v: %v", n.p, n.edges) - } - - case 4: - return parts[vAndH], nil - default: - return -1, fmt.Errorf("unexpected number of edges(%d) in node representing point %v", len(n.edges), n.p) - } -} - -// hVLineGraph represents lines on the canvas as a bidirectional graph of -// nodes. Helps to determine the characters that should be used where multiple -// lines cross. -type hVLineGraph struct { - nodes map[image.Point]*hVLineNode -} - -// newHVLineGraph creates a new hVLineGraph. -func newHVLineGraph() *hVLineGraph { - return &hVLineGraph{ - nodes: make(map[image.Point]*hVLineNode), - } -} - -// getOrCreateNode gets an existing or creates a new node for the point. -func (g *hVLineGraph) getOrCreateNode(p image.Point) *hVLineNode { - if n, ok := g.nodes[p]; ok { - return n - } - n := newHVLineNode(p) - g.nodes[p] = n - return n -} - -// addLine adds a line to the graph. -// This adds edges between all the points on the line. -func (g *hVLineGraph) addLine(line *hVLine) { - switch { - case line.horizontal(): - for curX := line.start.X; curX < line.end.X; curX++ { - from := image.Point{curX, line.start.Y} - to := image.Point{curX + 1, line.start.Y} - n1 := g.getOrCreateNode(from) - n2 := g.getOrCreateNode(to) - edge := newHVLineEdge(from, to) - n1.edges[edge] = true - n2.edges[edge] = true - } - - case line.vertical(): - for curY := line.start.Y; curY < line.end.Y; curY++ { - from := image.Point{line.start.X, curY} - to := image.Point{line.start.X, curY + 1} - n1 := g.getOrCreateNode(from) - n2 := g.getOrCreateNode(to) - edge := newHVLineEdge(from, to) - n1.edges[edge] = true - n2.edges[edge] = true - } - } -} - -// multiEdgeNodes returns all nodes that have more than one edge. These are -// the nodes where we might need to use different line characters to represent -// the crossing of multiple lines. -func (g *hVLineGraph) multiEdgeNodes() []*hVLineNode { - var nodes []*hVLineNode - for _, n := range g.nodes { - if len(n.edges) <= 1 { - continue - } - nodes = append(nodes, n) - } - return nodes -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/line_style.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/line_style.go deleted file mode 100644 index 41f1df4ee..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/line_style.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -import ( - "fmt" - - "github.com/mum4k/termdash/linestyle" - "github.com/mum4k/termdash/private/runewidth" -) - -// line_style.go contains the Unicode characters used for drawing lines of -// different styles. - -// lineStyleChars maps the line styles to the corresponding component characters. -// Source: http://en.wikipedia.org/wiki/Box-drawing_character. -var lineStyleChars = map[linestyle.LineStyle]map[linePart]rune{ - linestyle.Light: { - hLine: '─', - vLine: '│', - topLeftCorner: '┌', - topRightCorner: '┐', - bottomLeftCorner: '└', - bottomRightCorner: '┘', - hAndUp: '┴', - hAndDown: '┬', - vAndLeft: '┤', - vAndRight: '├', - vAndH: '┼', - }, - linestyle.Double: { - hLine: '═', - vLine: '║', - topLeftCorner: '╔', - topRightCorner: '╗', - bottomLeftCorner: '╚', - bottomRightCorner: '╝', - hAndUp: '╩', - hAndDown: '╦', - vAndLeft: '╣', - vAndRight: '╠', - vAndH: '╬', - }, - linestyle.Round: { - hLine: '─', - vLine: '│', - topLeftCorner: '╭', - topRightCorner: '╮', - bottomLeftCorner: '╰', - bottomRightCorner: '╯', - hAndUp: '┴', - hAndDown: '┬', - vAndLeft: '┤', - vAndRight: '├', - vAndH: '┼', - }, -} - -// init verifies that all line parts are half-width runes (occupy only one -// cell). -func init() { - for ls, parts := range lineStyleChars { - for part, r := range parts { - if got := runewidth.RuneWidth(r); got > 1 { - panic(fmt.Errorf("line style %v line part %v is a rune %c with width %v, all parts must be half-width runes (width of one)", ls, part, r, got)) - } - } - } -} - -// lineParts returns the line component characters for the provided line style. -func lineParts(ls linestyle.LineStyle) (map[linePart]rune, error) { - parts, ok := lineStyleChars[ls] - if !ok { - return nil, fmt.Errorf("unsupported line style %d", ls) - } - return parts, nil -} - -// linePart identifies individual line parts. -type linePart int - -// String implements fmt.Stringer() -func (lp linePart) String() string { - if n, ok := linePartNames[lp]; ok { - return n - } - return "linePartUnknown" -} - -// linePartNames maps linePart values to human readable names. -var linePartNames = map[linePart]string{ - vLine: "linePartVLine", - topLeftCorner: "linePartTopLeftCorner", - topRightCorner: "linePartTopRightCorner", - bottomLeftCorner: "linePartBottomLeftCorner", - bottomRightCorner: "linePartBottomRightCorner", - hAndUp: "linePartHAndUp", - hAndDown: "linePartHAndDown", - vAndLeft: "linePartVAndLeft", - vAndRight: "linePartVAndRight", - vAndH: "linePartVAndH", -} - -const ( - hLine linePart = iota - vLine - topLeftCorner - topRightCorner - bottomLeftCorner - bottomRightCorner - hAndUp - hAndDown - vAndLeft - vAndRight - vAndH -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/rectangle.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/rectangle.go deleted file mode 100644 index cd96ff715..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/rectangle.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// rectangle.go draws a rectangle. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas" -) - -// RectangleOption is used to provide options to the Rectangle function. -type RectangleOption interface { - // set sets the provided option. - set(*rectOptions) -} - -// rectOptions stores the provided options. -type rectOptions struct { - cellOpts []cell.Option - char rune -} - -// rectOption implements RectangleOption. -type rectOption func(rOpts *rectOptions) - -// set implements RectangleOption.set. -func (ro rectOption) set(rOpts *rectOptions) { - ro(rOpts) -} - -// RectCellOpts sets options on the cells that create the rectangle. -func RectCellOpts(opts ...cell.Option) RectangleOption { - return rectOption(func(rOpts *rectOptions) { - rOpts.cellOpts = append(rOpts.cellOpts, opts...) - }) -} - -// DefaultRectChar is the default value for the RectChar option. -const DefaultRectChar = ' ' - -// RectChar sets the character used in each of the cells of the rectangle. -func RectChar(c rune) RectangleOption { - return rectOption(func(rOpts *rectOptions) { - rOpts.char = c - }) -} - -// Rectangle draws a filled rectangle on the canvas. -func Rectangle(c *canvas.Canvas, r image.Rectangle, opts ...RectangleOption) error { - opt := &rectOptions{ - char: DefaultRectChar, - } - for _, o := range opts { - o.set(opt) - } - - if ar := c.Area(); !r.In(ar) { - return fmt.Errorf("the requested rectangle %v doesn't fit the canvas area %v", r, ar) - } - - if r.Dx() < 1 || r.Dy() < 1 { - return fmt.Errorf("the rectangle must be at least 1x1 cell, got %v", r) - } - - for col := r.Min.X; col < r.Max.X; col++ { - for row := r.Min.Y; row < r.Max.Y; row++ { - cells, err := c.SetCell(image.Point{col, row}, opt.char, opt.cellOpts...) - if err != nil { - return err - } - if cells != 1 { - return fmt.Errorf("invalid rectangle character %q, this character occupies %d cells, the implementation only supports half-width runes that occupy exactly one cell", opt.char, cells) - } - } - } - return nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/text.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/text.go deleted file mode 100644 index 17c4954a0..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/text.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// text.go contains code that prints UTF-8 encoded strings on the canvas. - -import ( - "fmt" - "image" - "strings" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas" - "github.com/mum4k/termdash/private/runewidth" -) - -// OverrunMode represents -type OverrunMode int - -// String implements fmt.Stringer() -func (om OverrunMode) String() string { - if n, ok := overrunModeNames[om]; ok { - return n - } - return "OverrunModeUnknown" -} - -// overrunModeNames maps OverrunMode values to human readable names. -var overrunModeNames = map[OverrunMode]string{ - OverrunModeStrict: "OverrunModeStrict", - OverrunModeTrim: "OverrunModeTrim", - OverrunModeThreeDot: "OverrunModeThreeDot", -} - -const ( - // OverrunModeStrict verifies that the drawn value fits the canvas and - // returns an error if it doesn't. - OverrunModeStrict OverrunMode = iota - - // OverrunModeTrim trims the part of the text that doesn't fit. - OverrunModeTrim - - // OverrunModeThreeDot trims the text and places the horizontal ellipsis - // '…' character at the end. - OverrunModeThreeDot -) - -// TextOption is used to provide options to Text(). -type TextOption interface { - // set sets the provided option. - set(*textOptions) -} - -// textOptions stores the provided options. -type textOptions struct { - cellOpts []cell.Option - maxX int - overrunMode OverrunMode -} - -// textOption implements TextOption. -type textOption func(*textOptions) - -// set implements TextOption.set. -func (to textOption) set(tOpts *textOptions) { - to(tOpts) -} - -// TextCellOpts sets options on the cells that contain the text. -func TextCellOpts(opts ...cell.Option) TextOption { - return textOption(func(tOpts *textOptions) { - tOpts.cellOpts = opts - }) -} - -// TextMaxX sets a limit on the X coordinate (column) of the drawn text. -// The X coordinate of all cells used by the text must be within -// start.X <= X < TextMaxX. -// If not provided, the width of the canvas is used as TextMaxX. -func TextMaxX(x int) TextOption { - return textOption(func(tOpts *textOptions) { - tOpts.maxX = x - }) -} - -// TextOverrunMode indicates what to do with text that overruns the TextMaxX() -// or the width of the canvas if TextMaxX() isn't specified. -// Defaults to OverrunModeStrict. -func TextOverrunMode(om OverrunMode) TextOption { - return textOption(func(tOpts *textOptions) { - tOpts.overrunMode = om - }) -} - -// TrimText trims the provided text so that it fits the specified amount of cells. -func TrimText(text string, maxCells int, om OverrunMode) (string, error) { - if maxCells < 1 { - return "", fmt.Errorf("maxCells(%d) cannot be less than one", maxCells) - } - - textCells := runewidth.StringWidth(text) - if textCells <= maxCells { - // Nothing to do if the text fits. - return text, nil - } - - switch om { - case OverrunModeStrict: - return "", fmt.Errorf("the requested text %q takes %d cells to draw, space is available for only %d cells and overrun mode is %v", text, textCells, maxCells, om) - case OverrunModeTrim, OverrunModeThreeDot: - default: - return "", fmt.Errorf("unsupported overrun mode %d", om) - } - - var b strings.Builder - cur := 0 - for _, r := range text { - rw := runewidth.RuneWidth(r) - if cur+rw >= maxCells { - switch { - case om == OverrunModeTrim: - // Only write the rune if it still fits, i.e. don't cut - // full-width runes in half. - if cur+rw == maxCells { - b.WriteRune(r) - } - case om == OverrunModeThreeDot: - b.WriteRune('…') - } - break - } - - b.WriteRune(r) - cur += rw - } - return b.String(), nil -} - -// Text prints the provided text on the canvas starting at the provided point. -func Text(c *canvas.Canvas, text string, start image.Point, opts ...TextOption) error { - ar := c.Area() - if !start.In(ar) { - return fmt.Errorf("the requested start point %v falls outside of the provided canvas %v", start, ar) - } - - opt := &textOptions{} - for _, o := range opts { - o.set(opt) - } - - if opt.maxX < 0 || opt.maxX > ar.Max.X { - return fmt.Errorf("invalid TextMaxX(%v), must be a positive number that is <= canvas.width %v", opt.maxX, ar.Dx()) - } - - var wantMaxX int - if opt.maxX == 0 { - wantMaxX = ar.Max.X - } else { - wantMaxX = opt.maxX - } - - maxCells := wantMaxX - start.X - trimmed, err := TrimText(text, maxCells, opt.overrunMode) - if err != nil { - return err - } - - cur := start - for _, r := range trimmed { - cells, err := c.SetCell(cur, r, opt.cellOpts...) - if err != nil { - return err - } - cur = image.Point{cur.X + cells, cur.Y} - } - return nil -} - -// ResizeNeeded draws an unicode character indicating that the canvas size is -// too small to draw meaningful content. -func ResizeNeeded(cvs *canvas.Canvas) error { - return Text(cvs, "⇄", image.Point{0, 0}) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/vertical_text.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/vertical_text.go deleted file mode 100644 index 44aadc9e5..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/draw/vertical_text.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package draw - -// vertical_text.go contains code that prints UTF-8 encoded strings on the -// canvas in vertical columns instead of lines. - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/canvas" -) - -// VerticalTextOption is used to provide options to Text(). -type VerticalTextOption interface { - // set sets the provided option. - set(*verticalTextOptions) -} - -// verticalTextOptions stores the provided options. -type verticalTextOptions struct { - cellOpts []cell.Option - maxY int - overrunMode OverrunMode -} - -// verticalTextOption implements VerticalTextOption. -type verticalTextOption func(*verticalTextOptions) - -// set implements VerticalTextOption.set. -func (vto verticalTextOption) set(vtOpts *verticalTextOptions) { - vto(vtOpts) -} - -// VerticalTextCellOpts sets options on the cells that contain the text. -func VerticalTextCellOpts(opts ...cell.Option) VerticalTextOption { - return verticalTextOption(func(vtOpts *verticalTextOptions) { - vtOpts.cellOpts = opts - }) -} - -// VerticalTextMaxY sets a limit on the Y coordinate (row) of the drawn text. -// The Y coordinate of all cells used by the vertical text must be within -// start.Y <= Y < VerticalTextMaxY. -// If not provided, the height of the canvas is used as VerticalTextMaxY. -func VerticalTextMaxY(y int) VerticalTextOption { - return verticalTextOption(func(vtOpts *verticalTextOptions) { - vtOpts.maxY = y - }) -} - -// VerticalTextOverrunMode indicates what to do with text that overruns the -// VerticalTextMaxY() or the width of the canvas if VerticalTextMaxY() isn't -// specified. -// Defaults to OverrunModeStrict. -func VerticalTextOverrunMode(om OverrunMode) VerticalTextOption { - return verticalTextOption(func(vtOpts *verticalTextOptions) { - vtOpts.overrunMode = om - }) -} - -// VerticalText prints the provided text on the canvas starting at the provided point. -// The text is printed in a vertical orientation, i.e: -// H -// e -// l -// l -// o -func VerticalText(c *canvas.Canvas, text string, start image.Point, opts ...VerticalTextOption) error { - ar := c.Area() - if !start.In(ar) { - return fmt.Errorf("the requested start point %v falls outside of the provided canvas %v", start, ar) - } - - opt := &verticalTextOptions{} - for _, o := range opts { - o.set(opt) - } - - if opt.maxY < 0 || opt.maxY > ar.Max.Y { - return fmt.Errorf("invalid VerticalTextMaxY(%v), must be a positive number that is <= canvas.width %v", opt.maxY, ar.Dy()) - } - - var wantMaxY int - if opt.maxY == 0 { - wantMaxY = ar.Max.Y - } else { - wantMaxY = opt.maxY - } - - maxCells := wantMaxY - start.Y - trimmed, err := TrimText(text, maxCells, opt.overrunMode) - if err != nil { - return err - } - - cur := start - for _, r := range trimmed { - cells, err := c.SetCell(cur, r, opt.cellOpts...) - if err != nil { - return err - } - cur = image.Point{cur.X, cur.Y + cells} - } - return nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/event/event.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/event/event.go deleted file mode 100644 index e9ef18dbb..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/event/event.go +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package event provides a non-blocking event distribution and subscription -// system. -package event - -import ( - "context" - "reflect" - "sync" - - "github.com/mum4k/termdash/private/event/eventqueue" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// Callback is a function provided by an event subscriber. -// It gets called with each event that passed the subscription filter. -// Implementations must be thread-safe, events come from a separate goroutine. -// Implementation should be light-weight, otherwise a slow-processing -// subscriber can build a long tail of events. -type Callback func(terminalapi.Event) - -// queue is a queue of terminal events. -type queue interface { - Push(e terminalapi.Event) - Pull(ctx context.Context) terminalapi.Event - Close() -} - -// subscriber represents a single subscriber. -type subscriber struct { - // cb is the callback the subscriber receives events on. - cb Callback - - // filter filters events towards the subscriber. - // An empty filter receives all events. - filter map[reflect.Type]bool - - // queue is a queue of events towards the subscriber. - queue queue - - // cancel when called terminates the goroutine that forwards events towards - // this subscriber. - cancel context.CancelFunc - - // processes is the number of events that were fully processed, i.e. - // delivered to the callback. - processed int - - // mu protects busy. - mu sync.Mutex -} - -// newSubscriber creates a new event subscriber. -func newSubscriber(filter []terminalapi.Event, cb Callback, opts *subscribeOptions) *subscriber { - f := map[reflect.Type]bool{} - for _, ev := range filter { - f[reflect.TypeOf(ev)] = true - } - - ctx, cancel := context.WithCancel(context.Background()) - var q queue - if opts.throttle { - q = eventqueue.NewThrottled(opts.maxRep) - } else { - q = eventqueue.New() - } - - s := &subscriber{ - cb: cb, - filter: f, - queue: q, - cancel: cancel, - } - - // Terminates when stop() is called. - go s.run(ctx) - return s -} - -// callback sends the event to the callback. -func (s *subscriber) callback(ev terminalapi.Event) { - s.cb(ev) - - func() { - s.mu.Lock() - defer s.mu.Unlock() - s.processed++ - }() -} - -// run periodically forwards events towards the subscriber. -// Terminates when the context expires. -func (s *subscriber) run(ctx context.Context) { - for { - ev := s.queue.Pull(ctx) - if ev != nil { - s.callback(ev) - } - - select { - case <-ctx.Done(): - return - default: - } - } -} - -// event forwards an event to the subscriber. -func (s *subscriber) event(ev terminalapi.Event) { - if len(s.filter) == 0 { - s.queue.Push(ev) - } - - t := reflect.TypeOf(ev) - if s.filter[t] { - s.queue.Push(ev) - } -} - -// processedEvents returns the number of events processed by this subscriber. -func (s *subscriber) processedEvents() int { - s.mu.Lock() - defer s.mu.Unlock() - return s.processed -} - -// stop stops the event subscriber. -func (s *subscriber) stop() { - s.cancel() - s.queue.Close() -} - -// DistributionSystem distributes events to subscribers. -// -// Subscribers can request filtering of events they get based on event type or -// subscribe to all events. -// -// The distribution system maintains a queue towards each subscriber, making -// sure that a single slow subscriber only slows itself down, rather than the -// entire application. -// -// This object is thread-safe. -type DistributionSystem struct { - // subscribers subscribe to events. - // maps subscriber id to subscriber. - subscribers map[int]*subscriber - - // nextID is id for the next subscriber. - nextID int - - // mu protects the distribution system. - mu sync.Mutex -} - -// NewDistributionSystem creates a new event distribution system. -func NewDistributionSystem() *DistributionSystem { - return &DistributionSystem{ - subscribers: map[int]*subscriber{}, - } -} - -// Event should be called with events coming from the terminal. -// The distribution system will distribute these to all the subscribers. -func (eds *DistributionSystem) Event(ev terminalapi.Event) { - eds.mu.Lock() - defer eds.mu.Unlock() - - for _, sub := range eds.subscribers { - sub.event(ev) - } -} - -// StopFunc when called unsubscribes the subscriber from all events and -// releases resources tied to the subscriber. -type StopFunc func() - -// SubscribeOption is used to provide options to Subscribe. -type SubscribeOption interface { - // set sets the provided option. - set(*subscribeOptions) -} - -// subscribeOptions stores the provided options. -type subscribeOptions struct { - throttle bool - maxRep int -} - -// subscribeOption implements Option. -type subscribeOption func(*subscribeOptions) - -// set implements SubscribeOption.set. -func (o subscribeOption) set(sOpts *subscribeOptions) { - o(sOpts) -} - -// MaxRepetitive when provided, instructs the system to drop repetitive -// events instead of delivering them. -// The argument maxRep indicates the maximum number of repetitive events to -// enqueue towards the subscriber. -func MaxRepetitive(maxRep int) SubscribeOption { - return subscribeOption(func(sOpts *subscribeOptions) { - sOpts.throttle = true - sOpts.maxRep = maxRep - }) -} - -// Subscribe subscribes to events according to the filter. -// An empty filter indicates that the subscriber wishes to receive events of -// all kinds. If the filter is non-empty, only events of the provided type will -// be sent to the subscriber. -// Returns a function that allows the subscriber to unsubscribe. -func (eds *DistributionSystem) Subscribe(filter []terminalapi.Event, cb Callback, opts ...SubscribeOption) StopFunc { - eds.mu.Lock() - defer eds.mu.Unlock() - - opt := &subscribeOptions{} - for _, o := range opts { - o.set(opt) - } - - id := eds.nextID - eds.nextID++ - sub := newSubscriber(filter, cb, opt) - eds.subscribers[id] = sub - - return func() { - eds.mu.Lock() - defer eds.mu.Unlock() - - sub.stop() - delete(eds.subscribers, id) - } -} - -// Processed returns the number of events that were fully processed, i.e. -// delivered to all the subscribers and their callbacks returned. -func (eds *DistributionSystem) Processed() int { - eds.mu.Lock() - defer eds.mu.Unlock() - - var res int - for _, sub := range eds.subscribers { - res += sub.processedEvents() - } - return res -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/event/eventqueue/eventqueue.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/event/eventqueue/eventqueue.go deleted file mode 100644 index eb22d4f2c..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/event/eventqueue/eventqueue.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package eventqueue provides an unboud FIFO queue of events. -package eventqueue - -import ( - "context" - "reflect" - "sync" - "time" - - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// node is a single data item on the queue. -type node struct { - prev *node - next *node - event terminalapi.Event -} - -// Unbound is an unbound FIFO queue of terminal events. -// Unbound must not be copied, pass it by reference only. -// This implementation is thread-safe. -type Unbound struct { - first *node - last *node - // mu protects first and last. - mu sync.Mutex - - // cond is used to notify any callers waiting on a call to Pull(). - cond *sync.Cond - - // condMU protects cond. - condMU sync.RWMutex - - // done is closed when the queue isn't needed anymore. - done chan struct{} -} - -// New returns a new Unbound queue of terminal events. -// Call Close() when done with the queue. -func New() *Unbound { - u := &Unbound{ - done: make(chan (struct{})), - } - u.cond = sync.NewCond(&u.condMU) - go u.wake() // Stops when Close() is called. - return u -} - -// wake periodically wakes up all goroutines waiting at Pull() so they can -// check if the context expired. -func (u *Unbound) wake() { - const spinTime = 250 * time.Millisecond - t := time.NewTicker(spinTime) - defer t.Stop() - for { - select { - case <-t.C: - u.cond.Broadcast() - case <-u.done: - return - } - } -} - -// Empty determines if the queue is empty. -func (u *Unbound) Empty() bool { - u.mu.Lock() - defer u.mu.Unlock() - return u.empty() -} - -// empty determines if the queue is empty. -func (u *Unbound) empty() bool { - return u.first == nil -} - -// Push pushes an event onto the queue. -func (u *Unbound) Push(e terminalapi.Event) { - u.mu.Lock() - defer u.mu.Unlock() - u.push(e) -} - -// push is the implementation of Push. -// Caller must hold u.mu. -func (u *Unbound) push(e terminalapi.Event) { - n := &node{ - event: e, - } - if u.empty() { - u.first = n - u.last = n - } else { - prev := u.last - u.last.next = n - u.last = n - u.last.prev = prev - } - u.cond.Signal() -} - -// Pop pops an event from the queue. Returns nil if the queue is empty. -func (u *Unbound) Pop() terminalapi.Event { - u.mu.Lock() - defer u.mu.Unlock() - - if u.empty() { - return nil - } - - n := u.first - u.first = u.first.next - - if u.empty() { - u.last = nil - } - return n.event -} - -// Pull is like Pop(), but blocks until an item is available or the context -// expires. Returns a nil event if the context expired. -func (u *Unbound) Pull(ctx context.Context) terminalapi.Event { - if e := u.Pop(); e != nil { - return e - } - - u.cond.L.Lock() - defer u.cond.L.Unlock() - for { - select { - case <-ctx.Done(): - return nil - default: - } - - if e := u.Pop(); e != nil { - return e - } - u.cond.Wait() - } -} - -// Close should be called when the queue isn't needed anymore. -func (u *Unbound) Close() { - close(u.done) -} - -// Throttled is an unbound and throttled FIFO queue of terminal events. -// Throttled must not be copied, pass it by reference only. -// This implementation is thread-safe. -type Throttled struct { - queue *Unbound - max int -} - -// NewThrottled returns a new Throttled queue of terminal events. -// -// This queue scans the queue content on each Push call and won't Push the -// event if there already is a continuous chain of exactly the same events -// en queued. The argument maxRep specifies the maximum number of repetitive -// events. -// -// Call Close() when done with the queue. -func NewThrottled(maxRep int) *Throttled { - t := &Throttled{ - queue: New(), - max: maxRep, - } - return t -} - -// Empty determines if the queue is empty. -func (t *Throttled) Empty() bool { - return t.queue.empty() -} - -// Push pushes an event onto the queue. -func (t *Throttled) Push(e terminalapi.Event) { - t.queue.mu.Lock() - defer t.queue.mu.Unlock() - - if t.queue.empty() { - t.queue.push(e) - return - } - - var same int - for n := t.queue.last; n != nil; n = n.prev { - if reflect.DeepEqual(e, n.event) { - same++ - } else { - break - } - - if same > t.max { - return // Drop the repetitive event. - } - } - t.queue.push(e) -} - -// Pop pops an event from the queue. Returns nil if the queue is empty. -func (t *Throttled) Pop() terminalapi.Event { - return t.queue.Pop() -} - -// Pull is like Pop(), but blocks until an item is available or the context -// expires. Returns a nil event if the context expired. -func (t *Throttled) Pull(ctx context.Context) terminalapi.Event { - return t.queue.Pull(ctx) -} - -// Close should be called when the queue isn't needed anymore. -func (t *Throttled) Close() { - close(t.queue.done) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/numbers/numbers.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/numbers/numbers.go deleted file mode 100644 index e91620f77..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/numbers/numbers.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package numbers implements various numerical functions. -package numbers - -import ( - "image" - "math" -) - -// RoundToNonZeroPlaces rounds the float up, so that it has at least the provided -// number of non-zero decimal places. -// Returns the rounded float and the number of leading decimal places that -// are zero. Returns the original float when places is zero. Negative places -// are treated as positive, so that -2 == 2. -func RoundToNonZeroPlaces(f float64, places int) (float64, int) { - if f == 0 { - return 0, 0 - } - - decOnly := zeroBeforeDecimal(f) - if decOnly == 0 { - return f, 0 - } - nzMult := multToNonZero(decOnly) - if places == 0 { - return f, multToPlaces(nzMult) - } - plMult := placesToMult(places) - - m := float64(nzMult * plMult) - return math.Ceil(f*m) / m, multToPlaces(nzMult) -} - -// multToNonZero returns multiplier for the float, so that the first decimal -// place is non-zero. The float must not be zero. -func multToNonZero(f float64) int { - v := f - if v < 0 { - v *= -1 - } - - mult := 1 - for v < 0.1 { - v *= 10 - mult *= 10 - } - return mult -} - -// placesToMult translates the number of decimal places to a multiple of 10. -func placesToMult(places int) int { - if places < 0 { - places *= -1 - } - - mult := 1 - for i := 0; i < places; i++ { - mult *= 10 - } - return mult -} - -// multToPlaces translates the multiple of 10 to a number of decimal places. -func multToPlaces(mult int) int { - places := 0 - for mult > 1 { - mult /= 10 - places++ - } - return places -} - -// zeroBeforeDecimal modifies the float so that it only has zero value before -// the decimal point. -func zeroBeforeDecimal(f float64) float64 { - var sign float64 = 1 - if f < 0 { - f *= -1 - sign = -1 - } - - floor := math.Floor(f) - return (f - floor) * sign -} - -// MinMax returns the smallest and the largest value among the provided values. -// Returns (0, 0) if there are no values. -// Ignores NaN values. Allowing NaN values could lead to a corner case where all -// values can be NaN, in this case the function will return NaN as min and max. -func MinMax(values []float64) (min, max float64) { - if len(values) == 0 { - return 0, 0 - } - min = math.MaxFloat64 - max = -1 * math.MaxFloat64 - allNaN := true - for _, v := range values { - if math.IsNaN(v) { - continue - } - allNaN = false - - if v < min { - min = v - } - if v > max { - max = v - } - } - - if allNaN { - return math.NaN(), math.NaN() - } - - return min, max -} - -// MinMaxInts returns the smallest and the largest int value among the provided -// values. Returns (0, 0) if there are no values. -func MinMaxInts(values []int) (min, max int) { - if len(values) == 0 { - return 0, 0 - } - min = math.MaxInt32 - max = -1 * math.MaxInt32 - - for _, v := range values { - if v < min { - min = v - } - if v > max { - max = v - } - } - return min, max -} - -// DegreesToRadians converts degrees to the equivalent in radians. -func DegreesToRadians(degrees int) float64 { - if degrees > 360 { - degrees %= 360 - } - return (float64(degrees) / 180) * math.Pi -} - -// RadiansToDegrees converts radians to the equivalent in degrees. -func RadiansToDegrees(radians float64) int { - d := int(math.Round(radians * 180 / math.Pi)) - if d < 0 { - d += 360 - } - return d -} - -// Abs returns the absolute value of x. -func Abs(x int) int { - if x < 0 { - return -x - } - return x -} - -// findGCF finds the greatest common factor of two integers. -func findGCF(a, b int) int { - if a == 0 || b == 0 { - return 0 - } - a = Abs(a) - b = Abs(b) - - // https://en.wikipedia.org/wiki/Euclidean_algorithm - for { - rem := a % b - a = b - b = rem - - if b == 0 { - break - } - } - return a -} - -// SimplifyRatio simplifies the given ratio. -func SimplifyRatio(ratio image.Point) image.Point { - gcf := findGCF(ratio.X, ratio.Y) - if gcf == 0 { - return image.ZP - } - return image.Point{ - X: ratio.X / gcf, - Y: ratio.Y / gcf, - } -} - -// SplitByRatio splits the provided number by the specified ratio. -func SplitByRatio(n int, ratio image.Point) image.Point { - sr := SimplifyRatio(ratio) - if sr.Eq(image.ZP) { - return image.ZP - } - fn := float64(n) - sum := float64(sr.X + sr.Y) - fact := fn / sum - return image.Point{ - int(math.Round(fact * float64(sr.X))), - int(math.Round(fact * float64(sr.Y))), - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/numbers/trig/trig.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/numbers/trig/trig.go deleted file mode 100644 index 16d179d02..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/numbers/trig/trig.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package trig implements various trigonometrical calculations. -package trig - -import ( - "fmt" - "image" - "math" - "sort" - - "github.com/mum4k/termdash/private/numbers" -) - -// CirclePointAtAngle given an angle in degrees and a circle midpoint and -// radius, calculates coordinates of a point on the circle at that angle. -// Angles are zero at the X axis and grow counter-clockwise. -func CirclePointAtAngle(degrees int, mid image.Point, radius int) image.Point { - angle := numbers.DegreesToRadians(degrees) - r := float64(radius) - x := mid.X + int(math.Round(r*math.Cos(angle))) - // Y coordinates grow down on the canvas. - y := mid.Y - int(math.Round(r*math.Sin(angle))) - return image.Point{x, y} -} - -// CircleAngleAtPoint given a point on a circle and its midpoint, -// calculates the angle in degrees. -// Angles are zero at the X axis and grow counter-clockwise. -func CircleAngleAtPoint(point, mid image.Point) int { - adj := float64(point.X - mid.X) - opp := float64(mid.Y - point.Y) - if opp != 0 { - angle := math.Atan2(opp, adj) - return numbers.RadiansToDegrees(angle) - } else if adj >= 0 { - return 0 - } else { - return 180 - } -} - -// PointIsIn asserts whether the provided point is inside of a shape outlined -// with the provided points. -// Does not verify that the shape is closed or complete, it merely counts the -// number of intersections with the shape on one row. -func PointIsIn(p image.Point, points []image.Point) bool { - maxX := p.X - set := map[image.Point]struct{}{} - for _, sp := range points { - set[sp] = struct{}{} - if sp.X > maxX { - maxX = sp.X - } - } - - if _, ok := set[p]; ok { - // Not inside if it is on the shape. - return false - } - - byY := map[int][]int{} // maps y->x - for p := range set { - byY[p.Y] = append(byY[p.Y], p.X) - } - for y := range byY { - sort.Ints(byY[y]) - } - - set = map[image.Point]struct{}{} - for y, xses := range byY { - set[image.Point{xses[0], y}] = struct{}{} - if len(xses) == 1 { - continue - } - - for i := 1; i < len(xses); i++ { - if xses[i] != xses[i-1]+1 { - set[image.Point{xses[i], y}] = struct{}{} - } - } - } - - crosses := 0 - for x := p.X; x <= maxX; x++ { - if _, ok := set[image.Point{x, p.Y}]; ok { - crosses++ - } - } - return crosses%2 != 0 -} - -const ( - // MinAngle is the smallest valid angle in degrees. - MinAngle = 0 - // MaxAngle is the largest valid angle in degrees. - MaxAngle = 360 -) - -// angleRange represents a range of angles in degrees. -// The range includes all angles such that start <= angle <= end. -type angleRange struct { - // start is the start if the range. - // This is always less or equal to the end. - start int - - // end is the end of the range. - end int -} - -// contains asserts whether the specified angle is in the range. -func (ar *angleRange) contains(angle int) bool { - return angle >= ar.start && angle <= ar.end -} - -// normalizeRange normalizes the start and end angles in degrees into ranges of -// angles. Useful for cases where the 0/360 point falls within the range. -// E.g: -// 0,25 => angleRange{0, 26} -// 0,360 => angleRange{0, 361} -// 359,20 => angleRange{359, 361}, angleRange{0, 21} -func normalizeRange(start, end int) ([]*angleRange, error) { - if start < MinAngle || start > MaxAngle { - return nil, fmt.Errorf("invalid start angle:%d, must be in range %d <= start <= %d", start, MinAngle, MaxAngle) - } - if end < MinAngle || end > MaxAngle { - return nil, fmt.Errorf("invalid end angle:%d, must be in range %d <= end <= %d", end, MinAngle, MaxAngle) - } - - if start == MaxAngle && end == 0 { - start, end = end, start - } - - if start <= end { - return []*angleRange{ - {start, end}, - }, nil - } - - // The range is crossing the 0/360 degree point. - // Break it into multiple ranges. - return []*angleRange{ - {start, MaxAngle}, - {0, end}, - }, nil -} - -// RangeSize returns the size of the degree range. -// E.g: -// 0,25 => 25 -// 359,1 => 2 -func RangeSize(start, end int) (int, error) { - ranges, err := normalizeRange(start, end) - if err != nil { - return 0, err - } - if len(ranges) == 1 { - return end - start, nil - } - return MaxAngle - start + end, nil -} - -// RangeMid returns an angle that lies in the middle between start and end. -// E.g: -// 0,10 => 5 -// 350,10 => 0 -func RangeMid(start, end int) (int, error) { - ranges, err := normalizeRange(start, end) - if err != nil { - return 0, err - } - if len(ranges) == 1 { - return start + ((end - start) / 2), nil - } - - length := MaxAngle - start + end - want := length / 2 - res := start + want - return res % MaxAngle, nil -} - -// FilterByAngle filters the provided points, returning only those that fall -// within the starting and the ending angle on a circle with the provided mid -// point. -func FilterByAngle(points []image.Point, mid image.Point, start, end int) ([]image.Point, error) { - var res []image.Point - ranges, err := normalizeRange(start, end) - if err != nil { - return nil, err - } - if mid.X < 0 || mid.Y < 0 { - return nil, fmt.Errorf("the mid point %v cannot have negative coordinates", mid) - } - - for _, p := range points { - angle := CircleAngleAtPoint(p, mid) - - // Edge case, this might mean 0 or 360. - // Decide based on where we are starting. - if angle == 0 && start > 0 { - angle = MaxAngle - } - - for _, r := range ranges { - if r.contains(angle) { - res = append(res, p) - break - } - } - } - return res, nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/runewidth/runewidth.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/runewidth/runewidth.go deleted file mode 100644 index 4f2f63a8f..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/runewidth/runewidth.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2019 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package runewidth is a wrapper over github.com/mattn/go-runewidth which -// gives different treatment to certain runes with ambiguous width. -package runewidth - -import runewidth "github.com/mattn/go-runewidth" - -// RuneWidth returns the number of cells needed to draw r. -// Background in http://www.unicode.org/reports/tr11/. -// -// Treats runes used internally by termdash as single-cell (half-width) runes -// regardless of the locale. I.e. runes that are used to draw lines, boxes, -// indicate resize or text trimming was needed and runes used by the braille -// canvas. -// -// This should be safe, since even in locales where these runes have ambiguous -// width, we still place all the character content around them so they should -// have be half-width. -func RuneWidth(r rune) int { - if inTable(r, exceptions) { - return 1 - } - return runewidth.RuneWidth(r) -} - -// StringWidth is like RuneWidth, but returns the number of cells occupied by -// all the runes in the string. -func StringWidth(s string) int { - var width int - for _, r := range []rune(s) { - width += RuneWidth(r) - } - return width -} - -// inTable determines if the rune falls within the table. -// Copied from github.com/mattn/go-runewidth/blob/master/runewidth.go. -func inTable(r rune, t table) bool { - // func (t table) IncludesRune(r rune) bool { - if r < t[0].first { - return false - } - - bot := 0 - top := len(t) - 1 - for top >= bot { - mid := (bot + top) >> 1 - - switch { - case t[mid].last < r: - bot = mid + 1 - case t[mid].first > r: - top = mid - 1 - default: - return true - } - } - - return false -} - -type interval struct { - first rune - last rune -} - -type table []interval - -// exceptions runes defined here are always considered to be half-width even if -// they might be ambiguous in some contexts. -var exceptions = table{ - // Characters used by termdash to indicate text trim or scroll. - {0x2026, 0x2026}, - {0x21c4, 0x21c4}, - {0x21e7, 0x21e7}, - {0x21e9, 0x21e9}, - - // Box drawing, used as line-styles. - // https://en.wikipedia.org/wiki/Box-drawing_character - {0x2500, 0x257F}, - - // Block elements used as sparks. - // https://en.wikipedia.org/wiki/Box-drawing_character - {0x2580, 0x258F}, -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/private/wrap/wrap.go b/examples/go-dashboard/src/github.com/mum4k/termdash/private/wrap/wrap.go deleted file mode 100644 index 5ee78a7a5..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/private/wrap/wrap.go +++ /dev/null @@ -1,409 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package wrap implements line wrapping at character or word boundaries. -package wrap - -import ( - "errors" - "fmt" - "strings" - "unicode" - - "github.com/mum4k/termdash/private/canvas/buffer" - "github.com/mum4k/termdash/private/runewidth" -) - -// Mode sets the wrapping mode. -type Mode int - -// String implements fmt.Stringer() -func (m Mode) String() string { - if n, ok := modeNames[m]; ok { - return n - } - return "ModeUnknown" -} - -// modeNames maps Mode values to human readable names. -var modeNames = map[Mode]string{ - Never: "WrapModeNever", - AtRunes: "WrapModeAtRunes", - AtWords: "WrapModeAtWords", -} - -const ( - // Never is the default wrapping mode, which disables line wrapping. - Never Mode = iota - - // AtRunes is a wrapping mode where if the width of the text crosses the - // width of the canvas, wrapping is performed at rune boundaries. - AtRunes - - // AtWords is a wrapping mode where if the width of the text crosses the - // width of the canvas, wrapping is performed at word boundaries. The - // wrapping still switches back to the AtRunes mode for any words that are - // longer than the width. - AtWords -) - -// ValidText validates the provided text for wrapping. -// The text must not be empty, contain any control or -// space characters other than '\n' and ' '. -func ValidText(text string) error { - if text == "" { - return errors.New("the text cannot be empty") - } - - for _, c := range text { - if c == ' ' || c == '\n' { // Allowed space and control runes. - continue - } - if unicode.IsControl(c) { - return fmt.Errorf("the provided text %q cannot contain control characters, found: %q", text, c) - } - if unicode.IsSpace(c) { - return fmt.Errorf("the provided text %q cannot contain space character %q", text, c) - } - } - return nil -} - -// ValidCells validates the provided cells for wrapping. -// The text in the cells must follow the same rules as described for ValidText. -func ValidCells(cells []*buffer.Cell) error { - var b strings.Builder - for _, c := range cells { - b.WriteRune(c.Rune) - } - return ValidText(b.String()) -} - -// Cells returns the cells wrapped into individual lines according to the -// specified width and wrapping mode. -// -// This function consumes any cells that contain newline characters and uses -// them to start new lines. -// -// If the mode is AtWords, this function also drops cells with leading space -// character before a word at which the wrap occurs. -func Cells(cells []*buffer.Cell, width int, m Mode) ([][]*buffer.Cell, error) { - if err := ValidCells(cells); err != nil { - return nil, err - } - switch m { - case Never: - case AtRunes: - case AtWords: - default: - return nil, fmt.Errorf("unsupported wrapping mode %v(%d)", m, m) - } - if width <= 0 { - return nil, nil - } - - cs := newCellScanner(cells, width, m) - for state := scanCellRunes; state != nil; state = state(cs) { - } - return cs.lines, nil -} - -// cellScannerState is a state in the FSM that scans the input text and identifies -// newlines. -type cellScannerState func(*cellScanner) cellScannerState - -// cellScanner tracks the progress of scanning the input cells when finding -// lines. -type cellScanner struct { - // cells are the cells being scanned. - cells []*buffer.Cell - - // nextIdx is the index of the cell that will be returned by next. - nextIdx int - - // wordStartIdx stores the starting index of the current word. - // A starting position of a word includes any leading space characters. - // E.g.: hello world - // ^ - // lastWordIdx - wordStartIdx int - // wordEndIdx stores the ending index of the current word. - // The word consists of all indexes that are - // wordStartIdx <= idx < wordEndIdx. - // A word also includes any punctuation after it. - wordEndIdx int - - // width is the width of the canvas the text will be drawn on. - width int - - // posX tracks the horizontal position of the current cell on the canvas. - posX int - - // mode is the wrapping mode. - mode Mode - - // atRunesInWord overrides the mode back to AtRunes. - atRunesInWord bool - - // lines are the identified lines. - lines [][]*buffer.Cell - - // line is the current line. - line []*buffer.Cell -} - -// newCellScanner returns a scanner of the provided cells. -func newCellScanner(cells []*buffer.Cell, width int, m Mode) *cellScanner { - return &cellScanner{ - cells: cells, - width: width, - mode: m, - } -} - -// next returns the next cell and advances the scanner. -// Returns nil when there are no more cells to scan. -func (cs *cellScanner) next() *buffer.Cell { - c := cs.peek() - if c != nil { - cs.nextIdx++ - } - return c -} - -// peek returns the next cell without advancing the scanner's position. -// Returns nil when there are no more cells to peek at. -func (cs *cellScanner) peek() *buffer.Cell { - if cs.nextIdx >= len(cs.cells) { - return nil - } - return cs.cells[cs.nextIdx] -} - -// peekPrev returns the previous cell without changing the scanner's position. -// Returns nil if the scanner is at the first cell. -func (cs *cellScanner) peekPrev() *buffer.Cell { - if cs.nextIdx == 0 { - return nil - } - return cs.cells[cs.nextIdx-1] -} - -// wordCells returns all the cells that belong to the current word. -func (cs *cellScanner) wordCells() []*buffer.Cell { - return cs.cells[cs.wordStartIdx:cs.wordEndIdx] -} - -// wordWidth returns the width of the current word in cells when printed on the -// terminal. -func (cs *cellScanner) wordWidth() int { - var b strings.Builder - for _, wc := range cs.wordCells() { - b.WriteRune(wc.Rune) - } - return runewidth.StringWidth(b.String()) -} - -// isWordStart determines if the scanner is at the beginning of a word. -func (cs *cellScanner) isWordStart() bool { - if cs.mode != AtWords { - return false - } - - current := cs.peekPrev() - next := cs.peek() - if current == nil || next == nil { - return false - } - - switch nr := next.Rune; { - case nr == '\n': - case nr == ' ': - default: - return true - } - return false -} - -// scanCellRunes scans the cells a rune at a time. -func scanCellRunes(cs *cellScanner) cellScannerState { - for { - cell := cs.next() - if cell == nil { - return scanEOF - } - - r := cell.Rune - if r == '\n' { - return newLineForLineBreak - } - - if cs.mode == Never { - return runeToCurrentLine - } - - if cs.atRunesInWord && !isWordCell(cell) { - cs.atRunesInWord = false - } - - if !cs.atRunesInWord && cs.isWordStart() { - return markWordStart - } - - if runeWrapNeeded(r, cs.posX, cs.width) { - return newLineForAtRunes - } - - return runeToCurrentLine - } -} - -// runeToCurrentLine scans a single cell rune onto the current line. -func runeToCurrentLine(cs *cellScanner) cellScannerState { - cell := cs.peekPrev() - // Move horizontally within the line for each scanned cell. - cs.posX += runewidth.RuneWidth(cell.Rune) - - // Copy the cell into the current line. - cs.line = append(cs.line, cell) - return scanCellRunes -} - -// newLineForLineBreak processes a newline character cell. -func newLineForLineBreak(cs *cellScanner) cellScannerState { - cs.lines = append(cs.lines, cs.line) - cs.posX = 0 - cs.line = nil - return scanCellRunes -} - -// newLineForAtRunes processes a line wrap at rune boundaries due to canvas width. -func newLineForAtRunes(cs *cellScanner) cellScannerState { - // The character on which we wrapped will be printed and is the start of - // new line. - cs.lines = append(cs.lines, cs.line) - cs.posX = runewidth.RuneWidth(cs.peekPrev().Rune) - cs.line = []*buffer.Cell{cs.peekPrev()} - return scanCellRunes -} - -// scanEOF terminates the scanning. -func scanEOF(cs *cellScanner) cellScannerState { - // Need to add the current line if it isn't empty, or if the previous rune - // was a newline. - // Newlines aren't copied onto the lines so just checking for emptiness - // isn't enough. We still want to include trailing empty newlines if - // they are in the input text. - if len(cs.line) > 0 || cs.peekPrev().Rune == '\n' { - cs.lines = append(cs.lines, cs.line) - } - return nil -} - -// markWordStart stores the starting position of the current word. -func markWordStart(cs *cellScanner) cellScannerState { - cs.wordStartIdx = cs.nextIdx - 1 - cs.wordEndIdx = cs.nextIdx - return scanWord -} - -// scanWord scans the entire word until it finds its end. -func scanWord(cs *cellScanner) cellScannerState { - for { - if isWordCell(cs.peek()) { - cs.next() - cs.wordEndIdx++ - continue - } - return wordToCurrentLine - } -} - -// wordToCurrentLine decides how to place the word into the output. -func wordToCurrentLine(cs *cellScanner) cellScannerState { - wordCells := cs.wordCells() - wordWidth := cs.wordWidth() - - if cs.posX+wordWidth <= cs.width { - // Place the word onto the current line. - cs.posX += wordWidth - cs.line = append(cs.line, wordCells...) - return scanCellRunes - } - return wrapWord -} - -// wrapWord wraps the word onto the next line or lines. -func wrapWord(cs *cellScanner) cellScannerState { - // Edge-case - the word starts the line and immediately doesn't fit. - if cs.posX > 0 { - cs.lines = append(cs.lines, cs.line) - cs.posX = 0 - cs.line = nil - } - - for i, wc := range cs.wordCells() { - if i == 0 && wc.Rune == ' ' { - // Skip the leading space when word wrapping. - continue - } - - if !runeWrapNeeded(wc.Rune, cs.posX, cs.width) { - cs.posX += runewidth.RuneWidth(wc.Rune) - cs.line = append(cs.line, wc) - continue - } - - // Replace the last placed rune with a dash indicating we wrapped the - // word. Only do this for half-width runes. - lastIdx := len(cs.line) - 1 - last := cs.line[lastIdx] - lastRW := runewidth.RuneWidth(last.Rune) - if cs.width > 1 && lastRW == 1 { - cs.line[lastIdx] = buffer.NewCell('-', last.Opts) - // Reset the scanner's position back to start scanning at the first - // rune of this word that wasn't placed. - cs.nextIdx = cs.wordStartIdx + i - 1 - } else { - // Edge-case width is one, no space to put the dash rune. - cs.nextIdx = cs.wordStartIdx + i - } - cs.atRunesInWord = true - return scanCellRunes - } - - cs.nextIdx = cs.wordEndIdx - return scanCellRunes -} - -// isWordCell determines if the cell contains a rune that belongs to a word. -func isWordCell(c *buffer.Cell) bool { - if c == nil { - return false - } - switch r := c.Rune; { - case r == '\n': - case r == ' ': - default: - return true - } - return false -} - -// runeWrapNeeded returns true if wrapping is needed for the rune at the horizontal -// position on the canvas that has the specified width. -func runeWrapNeeded(r rune, posX, width int) bool { - rw := runewidth.RuneWidth(r) - return posX > width-rw -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go b/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go deleted file mode 100644 index 8103ee717..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/* -Package termdash implements a terminal based dashboard. - -While running, the terminal dashboard performs the following: - - Periodic redrawing of the canvas and all the widgets. - - Event based redrawing of the widgets (i.e. on Keyboard or Mouse events). - - Forwards input events to widgets and optional subscribers. - - Handles terminal resize events. -*/ -package termdash - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/mum4k/termdash/container" - "github.com/mum4k/termdash/private/event" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// DefaultRedrawInterval is the default for the RedrawInterval option. -const DefaultRedrawInterval = 250 * time.Millisecond - -// Option is used to provide options. -type Option interface { - // set sets the provided option. - set(td *termdash) -} - -// option implements Option. -type option func(td *termdash) - -// set implements Option.set. -func (o option) set(td *termdash) { - o(td) -} - -// RedrawInterval sets how often termdash redraws the container and all the widgets. -// Defaults to DefaultRedrawInterval. Use the controller to disable the -// periodic redraw. -func RedrawInterval(t time.Duration) Option { - return option(func(td *termdash) { - td.redrawInterval = t - }) -} - -// ErrorHandler is used to provide a function that will be called with all -// errors that occur while the dashboard is running. If not provided, any -// errors panic the application. -// The provided function must be thread-safe. -func ErrorHandler(f func(error)) Option { - return option(func(td *termdash) { - td.errorHandler = f - }) -} - -// KeyboardSubscriber registers a subscriber for Keyboard events. Each -// keyboard event is forwarded to the container and the registered subscriber. -// The provided function must be thread-safe. -func KeyboardSubscriber(f func(*terminalapi.Keyboard)) Option { - return option(func(td *termdash) { - td.keyboardSubscriber = f - }) -} - -// MouseSubscriber registers a subscriber for Mouse events. Each mouse event -// is forwarded to the container and the registered subscriber. -// The provided function must be thread-safe. -func MouseSubscriber(f func(*terminalapi.Mouse)) Option { - return option(func(td *termdash) { - td.mouseSubscriber = f - }) -} - -// withEDS indicates that termdash should run with the provided event -// distribution system instead of creating one. -// Useful for tests. -func withEDS(eds *event.DistributionSystem) Option { - return option(func(td *termdash) { - td.eds = eds - }) -} - -// Run runs the terminal dashboard with the provided container on the terminal. -// Redraws the terminal periodically. If you prefer a manual redraw, use the -// Controller instead. -// Blocks until the context expires. -func Run(ctx context.Context, t terminalapi.Terminal, c *container.Container, opts ...Option) error { - td := newTermdash(t, c, opts...) - - err := td.start(ctx) - // Only return the status (error or nil) after the termdash event - // processing goroutine actually exits. - td.stop() - return err -} - -// Controller controls a termdash instance. -// The controller instance is only valid until Close() is called. -// The controller is not thread-safe. -type Controller struct { - td *termdash - cancel context.CancelFunc -} - -// NewController initializes termdash and returns an instance of the controller. -// Periodic redrawing is disabled when using the controller, the RedrawInterval -// option is ignored. -// Close the controller when it isn't needed anymore. -func NewController(t terminalapi.Terminal, c *container.Container, opts ...Option) (*Controller, error) { - ctx, cancel := context.WithCancel(context.Background()) - ctrl := &Controller{ - td: newTermdash(t, c, opts...), - cancel: cancel, - } - - // stops when Close() is called. - go ctrl.td.processEvents(ctx) - if err := ctrl.td.periodicRedraw(); err != nil { - return nil, err - } - return ctrl, nil -} - -// Redraw triggers redraw of the terminal. -func (c *Controller) Redraw() error { - if c.td == nil { - return errors.New("the termdash instance is no longer running, this controller is now invalid") - } - - c.td.mu.Lock() - defer c.td.mu.Unlock() - return c.td.redraw() -} - -// Close closes the Controller and its termdash instance. -func (c *Controller) Close() { - c.cancel() - c.td.stop() - c.td = nil -} - -// termdash is a terminal based dashboard. -// This object is thread-safe. -type termdash struct { - // term is the terminal the dashboard runs on. - term terminalapi.Terminal - - // container maintains terminal splits and places widgets. - container *container.Container - - // eds distributes input events to subscribers. - eds *event.DistributionSystem - - // closeCh gets closed when Stop() is called, which tells the event - // collecting goroutine to exit. - closeCh chan struct{} - // exitCh gets closed when the event collecting goroutine actually exits. - exitCh chan struct{} - - // clearNeeded indicates if the terminal needs to be cleared next time - // we're drawing it. Terminal needs to be cleared if its sized changed. - clearNeeded bool - - // mu protects termdash. - mu sync.Mutex - - // Options. - redrawInterval time.Duration - errorHandler func(error) - mouseSubscriber func(*terminalapi.Mouse) - keyboardSubscriber func(*terminalapi.Keyboard) -} - -// newTermdash creates a new termdash. -func newTermdash(t terminalapi.Terminal, c *container.Container, opts ...Option) *termdash { - td := &termdash{ - term: t, - container: c, - eds: event.NewDistributionSystem(), - closeCh: make(chan struct{}), - exitCh: make(chan struct{}), - redrawInterval: DefaultRedrawInterval, - } - - for _, opt := range opts { - opt.set(td) - } - td.subscribers() - c.Subscribe(td.eds) - return td -} - -// subscribers subscribes event receivers that live in this package to EDS. -func (td *termdash) subscribers() { - // Handler for all errors that occur during input event processing. - td.eds.Subscribe([]terminalapi.Event{terminalapi.NewError("")}, func(ev terminalapi.Event) { - td.handleError(ev.(*terminalapi.Error).Error()) - }) - - // Handles terminal resize events. - td.eds.Subscribe([]terminalapi.Event{&terminalapi.Resize{}}, func(terminalapi.Event) { - td.setClearNeeded() - }) - - // Redraws the screen on Keyboard and Mouse events. - // These events very likely change the content of the widgets (e.g. zooming - // a LineChart) so a redraw is needed to make that visible. - td.eds.Subscribe([]terminalapi.Event{ - &terminalapi.Keyboard{}, - &terminalapi.Mouse{}, - }, func(terminalapi.Event) { - td.evRedraw() - }, event.MaxRepetitive(0)) // No repetitive events that cause terminal redraw. - - // Keyboard and Mouse subscribers specified via options. - if td.keyboardSubscriber != nil { - td.eds.Subscribe([]terminalapi.Event{&terminalapi.Keyboard{}}, func(ev terminalapi.Event) { - td.keyboardSubscriber(ev.(*terminalapi.Keyboard)) - }) - } - if td.mouseSubscriber != nil { - td.eds.Subscribe([]terminalapi.Event{&terminalapi.Mouse{}}, func(ev terminalapi.Event) { - td.mouseSubscriber(ev.(*terminalapi.Mouse)) - }) - } -} - -// handleError forwards the error to the error handler if one was -// provided or panics. -func (td *termdash) handleError(err error) { - if td.errorHandler != nil { - td.errorHandler(err) - } else { - panic(err) - } -} - -// setClearNeeded flags that the terminal needs to be cleared next time we're -// drawing it. -func (td *termdash) setClearNeeded() { - td.mu.Lock() - defer td.mu.Unlock() - td.clearNeeded = true -} - -// redraw redraws the container and its widgets. -// The caller must hold td.mu. -func (td *termdash) redraw() error { - if td.clearNeeded { - if err := td.term.Clear(); err != nil { - return fmt.Errorf("term.Clear => error: %v", err) - } - td.clearNeeded = false - } - - if err := td.container.Draw(); err != nil { - return fmt.Errorf("container.Draw => error: %v", err) - } - - if err := td.term.Flush(); err != nil { - return fmt.Errorf("term.Flush => error: %v", err) - } - return nil -} - -// evRedraw redraws the container and its widgets. -func (td *termdash) evRedraw() error { - td.mu.Lock() - defer td.mu.Unlock() - - // Don't redraw immediately, give widgets that are performing enough time - // to update. - // We don't want to actually synchronize until all widgets update, we are - // purposefully leaving slow widgets behind. - time.Sleep(25 * time.Millisecond) - return td.redraw() -} - -// periodicRedraw is called once each RedrawInterval. -func (td *termdash) periodicRedraw() error { - td.mu.Lock() - defer td.mu.Unlock() - return td.redraw() -} - -// processEvents processes terminal input events. -// This is the body of the event collecting goroutine. -func (td *termdash) processEvents(ctx context.Context) { - defer close(td.exitCh) - - for { - ev := td.term.Event(ctx) - if ev != nil { - td.eds.Event(ev) - } - - select { - case <-ctx.Done(): - return - default: - } - } -} - -// start starts the terminal dashboard. Blocks until the context expires or -// until stop() is called. -func (td *termdash) start(ctx context.Context) error { - // Redraw once to initialize the container sizes. - if err := td.periodicRedraw(); err != nil { - close(td.exitCh) - return err - } - - redrawTimer := time.NewTicker(td.redrawInterval) - defer redrawTimer.Stop() - - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // stops when stop() is called or the context expires. - go td.processEvents(ctx) - - for { - select { - case <-redrawTimer.C: - if err := td.periodicRedraw(); err != nil { - return err - } - - case <-ctx.Done(): - return nil - - case <-td.closeCh: - return nil - } - } -} - -// stop tells the event collecting goroutine to stop. -// Blocks until it exits. -func (td *termdash) stop() { - close(td.closeCh) - <-td.exitCh -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/cell_options.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/cell_options.go deleted file mode 100644 index 41ee76013..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/cell_options.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package termbox - -// cell_options.go converts termdash cell options to the termbox format. - -import ( - "github.com/mum4k/termdash/cell" - tbx "github.com/nsf/termbox-go" -) - -// cellColor converts termdash cell color to the termbox format. -func cellColor(c cell.Color) tbx.Attribute { - return tbx.Attribute(c) -} - -// cellOptsToFg converts the cell options to the termbox foreground attribute. -func cellOptsToFg(opts *cell.Options) tbx.Attribute { - return cellColor(opts.FgColor) -} - -// cellOptsToBg converts the cell options to the termbox background attribute. -func cellOptsToBg(opts *cell.Options) tbx.Attribute { - return cellColor(opts.BgColor) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/color_mode.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/color_mode.go deleted file mode 100644 index 793f2a966..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/color_mode.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package termbox - -import ( - "fmt" - - "github.com/mum4k/termdash/terminal/terminalapi" - tbx "github.com/nsf/termbox-go" -) - -// colorMode converts termdash color modes to the termbox format. -func colorMode(cm terminalapi.ColorMode) (tbx.OutputMode, error) { - switch cm { - case terminalapi.ColorModeNormal: - return tbx.OutputNormal, nil - case terminalapi.ColorMode256: - return tbx.Output256, nil - case terminalapi.ColorMode216: - return tbx.Output216, nil - case terminalapi.ColorModeGrayscale: - return tbx.OutputGrayscale, nil - default: - return -1, fmt.Errorf("don't know how to convert color mode %v to the termbox format", cm) - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/event.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/event.go deleted file mode 100644 index c26d88c18..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/event.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package termbox - -// event.go converts termbox events to the termdash format. - -import ( - "image" - - "github.com/mum4k/termdash/keyboard" - "github.com/mum4k/termdash/mouse" - "github.com/mum4k/termdash/terminal/terminalapi" - tbx "github.com/nsf/termbox-go" -) - -// tbxToTd maps termbox key values to the termdash format. -var tbxToTd = map[tbx.Key]keyboard.Key{ - tbx.KeySpace: keyboard.KeySpace, - tbx.KeyF1: keyboard.KeyF1, - tbx.KeyF2: keyboard.KeyF2, - tbx.KeyF3: keyboard.KeyF3, - tbx.KeyF4: keyboard.KeyF4, - tbx.KeyF5: keyboard.KeyF5, - tbx.KeyF6: keyboard.KeyF6, - tbx.KeyF7: keyboard.KeyF7, - tbx.KeyF8: keyboard.KeyF8, - tbx.KeyF9: keyboard.KeyF9, - tbx.KeyF10: keyboard.KeyF10, - tbx.KeyF11: keyboard.KeyF11, - tbx.KeyF12: keyboard.KeyF12, - tbx.KeyInsert: keyboard.KeyInsert, - tbx.KeyDelete: keyboard.KeyDelete, - tbx.KeyHome: keyboard.KeyHome, - tbx.KeyEnd: keyboard.KeyEnd, - tbx.KeyPgup: keyboard.KeyPgUp, - tbx.KeyPgdn: keyboard.KeyPgDn, - tbx.KeyArrowUp: keyboard.KeyArrowUp, - tbx.KeyArrowDown: keyboard.KeyArrowDown, - tbx.KeyArrowLeft: keyboard.KeyArrowLeft, - tbx.KeyArrowRight: keyboard.KeyArrowRight, - tbx.KeyCtrlTilde: keyboard.KeyCtrlTilde, - tbx.KeyCtrlA: keyboard.KeyCtrlA, - tbx.KeyCtrlB: keyboard.KeyCtrlB, - tbx.KeyCtrlC: keyboard.KeyCtrlC, - tbx.KeyCtrlD: keyboard.KeyCtrlD, - tbx.KeyCtrlE: keyboard.KeyCtrlE, - tbx.KeyCtrlF: keyboard.KeyCtrlF, - tbx.KeyCtrlG: keyboard.KeyCtrlG, - tbx.KeyBackspace: keyboard.KeyBackspace, - tbx.KeyTab: keyboard.KeyTab, - tbx.KeyCtrlJ: keyboard.KeyCtrlJ, - tbx.KeyCtrlK: keyboard.KeyCtrlK, - tbx.KeyCtrlL: keyboard.KeyCtrlL, - tbx.KeyEnter: keyboard.KeyEnter, - tbx.KeyCtrlN: keyboard.KeyCtrlN, - tbx.KeyCtrlO: keyboard.KeyCtrlO, - tbx.KeyCtrlP: keyboard.KeyCtrlP, - tbx.KeyCtrlQ: keyboard.KeyCtrlQ, - tbx.KeyCtrlR: keyboard.KeyCtrlR, - tbx.KeyCtrlS: keyboard.KeyCtrlS, - tbx.KeyCtrlT: keyboard.KeyCtrlT, - tbx.KeyCtrlU: keyboard.KeyCtrlU, - tbx.KeyCtrlV: keyboard.KeyCtrlV, - tbx.KeyCtrlW: keyboard.KeyCtrlW, - tbx.KeyCtrlX: keyboard.KeyCtrlX, - tbx.KeyCtrlY: keyboard.KeyCtrlY, - tbx.KeyCtrlZ: keyboard.KeyCtrlZ, - tbx.KeyEsc: keyboard.KeyEsc, - tbx.KeyCtrl4: keyboard.KeyCtrl4, - tbx.KeyCtrl5: keyboard.KeyCtrl5, - tbx.KeyCtrl6: keyboard.KeyCtrl6, - tbx.KeyCtrl7: keyboard.KeyCtrl7, - tbx.KeyBackspace2: keyboard.KeyBackspace2, -} - -// convKey converts a termbox keyboard event to the termdash format. -func convKey(tbxEv tbx.Event) terminalapi.Event { - if tbxEv.Key != 0 && tbxEv.Ch != 0 { - return terminalapi.NewErrorf("the key event contain both a key(%v) and a character(%v)", tbxEv.Key, tbxEv.Ch) - } - - if tbxEv.Ch != 0 { - return &terminalapi.Keyboard{ - Key: keyboard.Key(tbxEv.Ch), - } - } - - k, ok := tbxToTd[tbxEv.Key] - if !ok { - return terminalapi.NewErrorf("unknown keyboard key '%v' in a keyboard event", k) - } - return &terminalapi.Keyboard{ - Key: k, - } -} - -// convMouse converts a termbox mouse event to the termdash format. -func convMouse(tbxEv tbx.Event) terminalapi.Event { - var button mouse.Button - - switch k := tbxEv.Key; k { - case tbx.MouseLeft: - button = mouse.ButtonLeft - case tbx.MouseMiddle: - button = mouse.ButtonMiddle - case tbx.MouseRight: - button = mouse.ButtonRight - case tbx.MouseRelease: - button = mouse.ButtonRelease - case tbx.MouseWheelUp: - button = mouse.ButtonWheelUp - case tbx.MouseWheelDown: - button = mouse.ButtonWheelDown - default: - return terminalapi.NewErrorf("unknown mouse key %v in a mouse event", k) - } - - return &terminalapi.Mouse{ - Position: image.Point{tbxEv.MouseX, tbxEv.MouseY}, - Button: button, - } -} - -// convResize converts a termbox resize event to the termdash format. -func convResize(tbxEv tbx.Event) terminalapi.Event { - size := image.Point{tbxEv.Width, tbxEv.Height} - if size.X < 0 || size.Y < 0 { - return terminalapi.NewErrorf("terminal resized to negative size: %v", size) - } - return &terminalapi.Resize{ - Size: size, - } -} - -// toTermdashEvents converts a termbox event to the termdash event format. -func toTermdashEvents(tbxEv tbx.Event) []terminalapi.Event { - switch t := tbxEv.Type; t { - case tbx.EventInterrupt: - return []terminalapi.Event{ - terminalapi.NewError("event type EventInterrupt isn't supported"), - } - case tbx.EventRaw: - return []terminalapi.Event{ - terminalapi.NewError("event type EventRaw isn't supported"), - } - case tbx.EventNone: - return []terminalapi.Event{ - terminalapi.NewError("event type EventNone isn't supported"), - } - case tbx.EventError: - return []terminalapi.Event{ - terminalapi.NewErrorf("input error occurred: %v", tbxEv.Err), - } - case tbx.EventResize: - return []terminalapi.Event{convResize(tbxEv)} - case tbx.EventMouse: - return []terminalapi.Event{convMouse(tbxEv)} - case tbx.EventKey: - return []terminalapi.Event{ - convKey(tbxEv), - } - default: - return []terminalapi.Event{ - terminalapi.NewErrorf("unknown termbox event type: %v", t), - } - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/termbox.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/termbox.go deleted file mode 100644 index 4329e4686..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/termbox/termbox.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package termbox implements terminal using the nsf/termbox-go library. -package termbox - -import ( - "context" - "image" - - "github.com/mum4k/termdash/cell" - "github.com/mum4k/termdash/private/event/eventqueue" - "github.com/mum4k/termdash/terminal/terminalapi" - tbx "github.com/nsf/termbox-go" -) - -// Option is used to provide options. -type Option interface { - // set sets the provided option. - set(*Terminal) -} - -// option implements Option. -type option func(*Terminal) - -// set implements Option.set. -func (o option) set(t *Terminal) { - o(t) -} - -// DefaultColorMode is the default value for the ColorMode option. -const DefaultColorMode = terminalapi.ColorMode256 - -// ColorMode sets the terminal color mode. -// Defaults to DefaultColorMode. -func ColorMode(cm terminalapi.ColorMode) Option { - return option(func(t *Terminal) { - t.colorMode = cm - }) -} - -// Terminal provides input and output to a real terminal. Wraps the -// nsf/termbox-go terminal implementation. This object is not thread-safe. -// Implements terminalapi.Terminal. -type Terminal struct { - // events is a queue of input events. - events *eventqueue.Unbound - - // done gets closed when Close() is called. - done chan struct{} - - // Options. - colorMode terminalapi.ColorMode -} - -// newTerminal creates the terminal and applies the options. -func newTerminal(opts ...Option) *Terminal { - t := &Terminal{ - events: eventqueue.New(), - done: make(chan struct{}), - colorMode: DefaultColorMode, - } - for _, opt := range opts { - opt.set(t) - } - return t -} - -// New returns a new termbox based Terminal. -// Call Close() when the terminal isn't required anymore. -func New(opts ...Option) (*Terminal, error) { - if err := tbx.Init(); err != nil { - return nil, err - } - tbx.SetInputMode(tbx.InputEsc | tbx.InputMouse) - - t := newTerminal(opts...) - om, err := colorMode(t.colorMode) - if err != nil { - return nil, err - } - tbx.SetOutputMode(om) - - go t.pollEvents() // Stops when Close() is called. - return t, nil -} - -// Size implements terminalapi.Terminal.Size. -func (t *Terminal) Size() image.Point { - w, h := tbx.Size() - return image.Point{w, h} -} - -// Clear implements terminalapi.Terminal.Clear. -func (t *Terminal) Clear(opts ...cell.Option) error { - o := cell.NewOptions(opts...) - return tbx.Clear(cellOptsToFg(o), cellOptsToBg(o)) -} - -// Flush implements terminalapi.Terminal.Flush. -func (t *Terminal) Flush() error { - return tbx.Flush() -} - -// SetCursor implements terminalapi.Terminal.SetCursor. -func (t *Terminal) SetCursor(p image.Point) { - tbx.SetCursor(p.X, p.Y) -} - -// HideCursor implements terminalapi.Terminal.HideCursor. -func (t *Terminal) HideCursor() { - tbx.HideCursor() -} - -// SetCell implements terminalapi.Terminal.SetCell. -func (t *Terminal) SetCell(p image.Point, r rune, opts ...cell.Option) error { - o := cell.NewOptions(opts...) - tbx.SetCell(p.X, p.Y, r, cellOptsToFg(o), cellOptsToBg(o)) - return nil -} - -// pollEvents polls and enqueues the input events. -func (t *Terminal) pollEvents() { - for { - select { - case <-t.done: - return - default: - } - - events := toTermdashEvents(tbx.PollEvent()) - for _, ev := range events { - t.events.Push(ev) - } - } -} - -// Event implements terminalapi.Terminal.Event. -func (t *Terminal) Event(ctx context.Context) terminalapi.Event { - ev := t.events.Pull(ctx) - if ev == nil { - return nil - } - return ev -} - -// Close closes the terminal, should be called when the terminal isn't required -// anymore to return the screen to a sane state. -// Implements terminalapi.Terminal.Close. -func (t *Terminal) Close() { - close(t.done) - tbx.Close() -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/color_mode.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/color_mode.go deleted file mode 100644 index 28c93277f..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/color_mode.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package terminalapi - -// color_mode.go defines the terminal color modes. - -// ColorMode represents a color mode of a terminal. -type ColorMode int - -// String implements fmt.Stringer() -func (cm ColorMode) String() string { - if n, ok := colorModeNames[cm]; ok { - return n - } - return "ColorModeUnknown" -} - -// colorModeNames maps ColorMode values to human readable names. -var colorModeNames = map[ColorMode]string{ - ColorModeNormal: "ColorModeNormal", - ColorMode256: "ColorMode256", - ColorMode216: "ColorMode216", - ColorModeGrayscale: "ColorModeGrayscale", -} - -// Supported color modes. -const ( - // ColorModeNormal supports 8 "system" colors. - // These are defined as constants in the cell package. - ColorModeNormal ColorMode = iota - - // ColorMode256 enables using any of the 256 terminal colors. - // 0-7: the 8 "system" colors accessible in ColorModeNormal. - // 8-15: the 8 "bright system" colors. - // 16-231: the 216 different terminal colors. - // 232-255: the 24 different shades of grey. - ColorMode256 - - // ColorMode216 supports only the third range of the ColorMode256, i.e the - // 216 different terminal colors. However in this mode the colors are zero - // based, so the caller doesn't need to provide an offset. - ColorMode216 - - // ColorModeGrayscale supports only the fourth range of the ColorMode256, - // i.e the 24 different shades of grey. However in this mode the colors are - // zero based, so the caller doesn't need to provide an offset. - ColorModeGrayscale -) diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/event.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/event.go deleted file mode 100644 index a543e8433..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/event.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package terminalapi - -import ( - "errors" - "fmt" - "image" - - "github.com/mum4k/termdash/keyboard" - "github.com/mum4k/termdash/mouse" -) - -// event.go defines events that can be received through the terminal API. - -// Event represents an input event. -type Event interface { - isEvent() -} - -// Keyboard is the event used when a key is pressed. -// Implements terminalapi.Event. -type Keyboard struct { - // Key is the pressed key. - Key keyboard.Key -} - -func (*Keyboard) isEvent() {} - -// String implements fmt.Stringer. -func (k Keyboard) String() string { - return fmt.Sprintf("Keyboard{Key: %v}", k.Key) -} - -// Resize is the event used when the terminal was resized. -// Implements terminalapi.Event. -type Resize struct { - // Size is the new size of the terminal. - Size image.Point -} - -func (*Resize) isEvent() {} - -// String implements fmt.Stringer. -func (r Resize) String() string { - return fmt.Sprintf("Resize{Size: %v}", r.Size) -} - -// Mouse is the event used when the mouse is moved or a mouse button is -// pressed. -// Implements terminalapi.Event. -type Mouse struct { - // Position of the mouse on the terminal. - Position image.Point - // Button identifies the pressed button if any. - Button mouse.Button -} - -func (*Mouse) isEvent() {} - -// String implements fmt.Stringer. -func (m Mouse) String() string { - return fmt.Sprintf("Mouse{Position: %v, Button: %v}", m.Position, m.Button) -} - -// Error is an event indicating an error while processing input. -type Error string - -// NewError returns a new Error event. -func NewError(e string) *Error { - err := Error(e) - return &err -} - -// NewErrorf returns a new Error event, arguments are similar to fmt.Sprintf. -func NewErrorf(format string, args ...interface{}) *Error { - err := Error(fmt.Sprintf(format, args...)) - return &err -} - -func (*Error) isEvent() {} - -// Error returns the error that occurred. -func (e *Error) Error() error { - if e == nil || *e == "" { - return nil - } - return errors.New(string(*e)) -} - -// String implements fmt.Stringer. -func (e Error) String() string { - return string(e) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/terminalapi.go b/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/terminalapi.go deleted file mode 100644 index 831abc169..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/terminal/terminalapi/terminalapi.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package terminalapi defines the API of all terminal implementations. -package terminalapi - -import ( - "context" - "image" - - "github.com/mum4k/termdash/cell" -) - -// Terminal abstracts an implementation of a 2-D terminal. -// A terminal consists of a number of cells. -type Terminal interface { - // Size returns the terminal width and height in cells. - Size() image.Point - - // Clear clears the content of the internal back buffer, resetting all - // cells to their default content and attributes. Sets the provided options - // on all the cell. - Clear(opts ...cell.Option) error - // Flush flushes the internal back buffer to the terminal. - Flush() error - - // SetCursor sets the position of the cursor. - SetCursor(p image.Point) - // HideCursos hides the cursor. - HideCursor() - - // SetCell sets the value of the specified cell to the provided rune. - // Use the options to specify which attributes to modify, if an attribute - // option isn't specified, the attribute retains its previous value. - SetCell(p image.Point, r rune, opts ...cell.Option) error - - // Event waits for the next event and returns it. - // This call blocks until the next event or cancellation of the context. - // Returns nil when the context gets canceled. - Event(ctx context.Context) Event - - // Close closes the underlying terminal implementation and should be called when - // the terminal isn't required anymore to return the screen to a sane state. - Close() -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/widgetapi/widgetapi.go b/examples/go-dashboard/src/github.com/mum4k/termdash/widgetapi/widgetapi.go deleted file mode 100644 index ee27136a1..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/widgetapi/widgetapi.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package widgetapi defines the API of a widget on the dashboard. -package widgetapi - -import ( - "image" - - "github.com/mum4k/termdash/private/canvas" - "github.com/mum4k/termdash/terminal/terminalapi" -) - -// KeyScope indicates the scope at which the widget wants to receive keyboard -// events. -type KeyScope int - -// String implements fmt.Stringer() -func (ks KeyScope) String() string { - if n, ok := keyScopeNames[ks]; ok { - return n - } - return "KeyScopeUnknown" -} - -// keyScopeNames maps KeyScope values to human readable names. -var keyScopeNames = map[KeyScope]string{ - KeyScopeNone: "KeyScopeNone", - KeyScopeFocused: "KeyScopeFocused", - KeyScopeGlobal: "KeyScopeGlobal", -} - -const ( - // KeyScopeNone is used when the widget doesn't want to receive any - // keyboard events. - KeyScopeNone KeyScope = iota - - // KeyScopeFocused is used when the widget wants to only receive keyboard - // events when its container is focused. - KeyScopeFocused - - // KeyScopeGlobal is used when the widget wants to receive all keyboard - // events regardless of which container is focused. - KeyScopeGlobal -) - -// MouseScope indicates the scope at which the widget wants to receive mouse -// events. -type MouseScope int - -// String implements fmt.Stringer() -func (ms MouseScope) String() string { - if n, ok := mouseScopeNames[ms]; ok { - return n - } - return "MouseScopeUnknown" -} - -// mouseScopeNames maps MouseScope values to human readable names. -var mouseScopeNames = map[MouseScope]string{ - MouseScopeNone: "MouseScopeNone", - MouseScopeWidget: "MouseScopeWidget", - MouseScopeContainer: "MouseScopeContainer", - MouseScopeGlobal: "MouseScopeGlobal", -} - -const ( - // MouseScopeNone is used when the widget doesn't want to receive any mouse - // events. - MouseScopeNone MouseScope = iota - - // MouseScopeWidget is used when the widget only wants mouse events that - // fall onto its canvas. - // The position of these widgets is always relative to widget's canvas. - MouseScopeWidget - - // MouseScopeContainer is used when the widget only wants mouse events that - // fall onto its container. The area size of a container is always larger - // or equal to the one of the widget's canvas. So a widget selecting - // MouseScopeContainer will either receive the same or larger amount of - // events as compared to MouseScopeWidget. - // The position of mouse events that fall outside of widget's canvas is - // reset to image.Point{-1, -1}. - // The widgets are allowed to process the button event. - MouseScopeContainer - - // MouseScopeGlobal is used when the widget wants to receive all mouse - // events regardless on where on the terminal they land. - // The position of mouse events that fall outside of widget's canvas is - // reset to image.Point{-1, -1} and must not be used by the widgets. - // The widgets are allowed to process the button event. - MouseScopeGlobal -) - -// Options contains registration options for a widget. -// This is how the widget indicates its needs to the infrastructure. -type Options struct { - // Ratio allows a widget to request a canvas whose size will always have - // the specified ratio of width:height (Ratio.X:Ratio.Y). - // The zero value i.e. image.Point{0, 0} indicates that the widget accepts - // canvas of any ratio. - Ratio image.Point - - // MinimumSize allows a widget to specify the smallest allowed canvas size. - // If the terminal size and/or splits cause the assigned canvas to be - // smaller than this, the widget will be skipped. I.e. The Draw() method - // won't be called until a resize above the specified minimum. - MinimumSize image.Point - - // MaximumSize allows a widget to specify the largest allowed canvas size. - // If the terminal size and/or splits cause the assigned canvas to be larger - // than this, the widget will only receive a canvas of this size within its - // container. Setting any of the two coordinates to zero indicates - // unlimited. - MaximumSize image.Point - - // WantKeyboard allows a widget to request keyboard events and specify - // their desired scope. If set to KeyScopeNone, no keyboard events are - // forwarded to the widget. - WantKeyboard KeyScope - - // WantMouse allows a widget to request mouse events and specify their - // desired scope. If set to MouseScopeNone, no mouse events are forwarded - // to the widget. - // Note that the widget is only able to see the position of the mouse event - // if it falls onto its canvas. See the documentation next to individual - // MouseScope values for details. - WantMouse MouseScope -} - -// Meta provide additional metadata to widgets. -type Meta struct { - // Focused asserts whether the widget's container is focused. - Focused bool -} - -// Widget is a single widget on the dashboard. -// Implementations must be thread safe. -type Widget interface { - // When the infrastructure calls Draw(), the widget must block on the call - // until it finishes drawing onto the provided canvas. When given the - // canvas, the widget must first determine its size by calling - // Canvas.Size(), then limit all its drawing to this area. - // - // The widget must not assume that the size of the canvas or its content - // remains the same between calls. - // - // The argument meta is guaranteed to be valid (i.e. non-nil). - Draw(cvs *canvas.Canvas, meta *Meta) error - - // Keyboard is called when the widget is focused on the dashboard and a key - // shortcut the widget registered for was pressed. Only called if the widget - // registered for keyboard events. - Keyboard(k *terminalapi.Keyboard) error - - // Mouse is called when the widget is focused on the dashboard and a mouse - // event happens on its canvas. Only called if the widget registered for mouse - // events. - Mouse(m *terminalapi.Mouse) error - - // Options returns registration options for the widget. - // This is how the widget indicates to the infrastructure whether it is - // interested in keyboard or mouse shortcuts, what is its minimum canvas - // size, etc. - // - // Most widgets will return statically compiled options (minimum and - // maximum size, etc.). If the returned options depend on the runtime state - // of the widget (e.g. the user data provided to the widget), the widget - // must protect against a case where the infrastructure calls the Draw - // method with a canvas that doesn't meet the requested options. This is - // because the data in the widget might change between calls to Options and - // Draw. - Options() Options -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/line_trim.go b/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/line_trim.go deleted file mode 100644 index 6ca8c83aa..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/line_trim.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package text - -import ( - "fmt" - "image" - - "github.com/mum4k/termdash/private/canvas" - "github.com/mum4k/termdash/private/runewidth" - "github.com/mum4k/termdash/private/wrap" -) - -// line_trim.go contains code that trims lines that are too long. - -type trimResult struct { - // trimmed is set to true if the current and the following runes on this - // line are trimmed. - trimmed bool - - // curPoint is the updated current point the drawing should continue on. - curPoint image.Point -} - -// drawTrimChar draws the horizontal ellipsis '…' character as the last -// character in the canvas on the specified line. -func drawTrimChar(cvs *canvas.Canvas, line int) error { - lastPoint := image.Point{cvs.Area().Dx() - 1, line} - // If the penultimate cell contains a full-width rune, we need to clear it - // first. Otherwise the trim char would cover just half of it. - if width := cvs.Area().Dx(); width > 1 { - penUlt := image.Point{width - 2, line} - prev, err := cvs.Cell(penUlt) - if err != nil { - return err - } - - if runewidth.RuneWidth(prev.Rune) == 2 { - if _, err := cvs.SetCell(penUlt, 0); err != nil { - return err - } - } - } - - cells, err := cvs.SetCell(lastPoint, '…') - if err != nil { - return err - } - if cells != 1 { - panic(fmt.Errorf("invalid trim character, it occupies %d cells, the implementation only supports scroll markers that occupy exactly one cell", cells)) - } - return nil -} - -// lineTrim determines if the current line needs to be trimmed. The cvs is the -// canvas assigned to the widget, the curPoint is the current point the widget -// is going to place the curRune at. If line trimming is needed, this function -// replaces the last character with the horizontal ellipsis '…' character. -func lineTrim(cvs *canvas.Canvas, curPoint image.Point, curRune rune, opts *options) (*trimResult, error) { - if opts.wrapMode == wrap.AtRunes { - // Don't trim if the widget is configured to wrap lines. - return &trimResult{ - trimmed: false, - curPoint: curPoint, - }, nil - } - - // Newline characters are never trimmed, they start the next line. - if curRune == '\n' { - return &trimResult{ - trimmed: false, - curPoint: curPoint, - }, nil - } - - width := cvs.Area().Dx() - rw := runewidth.RuneWidth(curRune) - switch { - case rw == 1: - if curPoint.X == width { - if err := drawTrimChar(cvs, curPoint.Y); err != nil { - return nil, err - } - } - - case rw == 2: - if curPoint.X == width || curPoint.X == width-1 { - if err := drawTrimChar(cvs, curPoint.Y); err != nil { - return nil, err - } - } - - default: - return nil, fmt.Errorf("unable to decide line trimming at position %v for rune %q which has an unsupported width %d", curPoint, curRune, rw) - } - - trimmed := curPoint.X > width-rw - if trimmed { - curPoint = image.Point{curPoint.X + rw, curPoint.Y} - } - return &trimResult{ - trimmed: trimmed, - curPoint: curPoint, - }, nil -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/options.go b/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/options.go deleted file mode 100644 index b91cec85e..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/options.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package text - -import ( - "fmt" - - "github.com/mum4k/termdash/keyboard" - "github.com/mum4k/termdash/mouse" - "github.com/mum4k/termdash/private/wrap" -) - -// options.go contains configurable options for Text. - -// Option is used to provide options to New(). -type Option interface { - // set sets the provided option. - set(*options) -} - -// options stores the provided options. -type options struct { - wrapMode wrap.Mode - rollContent bool - disableScrolling bool - mouseUpButton mouse.Button - mouseDownButton mouse.Button - keyUp keyboard.Key - keyDown keyboard.Key - keyPgUp keyboard.Key - keyPgDown keyboard.Key -} - -// newOptions returns a new options instance. -func newOptions(opts ...Option) *options { - opt := &options{ - mouseUpButton: DefaultScrollMouseButtonUp, - mouseDownButton: DefaultScrollMouseButtonDown, - keyUp: DefaultScrollKeyUp, - keyDown: DefaultScrollKeyDown, - keyPgUp: DefaultScrollKeyPageUp, - keyPgDown: DefaultScrollKeyPageDown, - } - for _, o := range opts { - o.set(opt) - } - return opt -} - -// validate validates the provided options. -func (o *options) validate() error { - keys := map[keyboard.Key]bool{ - o.keyUp: true, - o.keyDown: true, - o.keyPgUp: true, - o.keyPgDown: true, - } - if len(keys) != 4 { - return fmt.Errorf("invalid ScrollKeys(up:%v, down:%v, pageUp:%v, pageDown:%v), the keys must be unique", o.keyUp, o.keyDown, o.keyPgUp, o.keyPgDown) - } - if o.mouseUpButton == o.mouseDownButton { - return fmt.Errorf("invalid ScrollMouseButtons(up:%v, down:%v), the buttons must be unique", o.mouseUpButton, o.mouseDownButton) - } - return nil -} - -// option implements Option. -type option func(*options) - -// set implements Option.set. -func (o option) set(opts *options) { - o(opts) -} - -// WrapAtWords configures the text widget so that it automatically wraps lines -// that are longer than the width of the widget at word boundaries. If not -// provided, long lines are trimmed instead. -func WrapAtWords() Option { - return option(func(opts *options) { - opts.wrapMode = wrap.AtWords - }) -} - -// WrapAtRunes configures the text widget so that it automatically wraps lines -// that are longer than the width of the widget at rune boundaries. If not -// provided, long lines are trimmed instead. -func WrapAtRunes() Option { - return option(func(opts *options) { - opts.wrapMode = wrap.AtRunes - }) -} - -// RollContent configures the text widget so that it rolls the text content up -// if more text than the size of the container is added. If not provided, the -// content is trimmed instead. -func RollContent() Option { - return option(func(opts *options) { - opts.rollContent = true - }) -} - -// DisableScrolling disables the scrolling of the content using keyboard and -// mouse. -func DisableScrolling() Option { - return option(func(opts *options) { - opts.disableScrolling = true - }) -} - -// The default mouse buttons for content scrolling. -const ( - DefaultScrollMouseButtonUp = mouse.ButtonWheelUp - DefaultScrollMouseButtonDown = mouse.ButtonWheelDown -) - -// ScrollMouseButtons configures the mouse buttons that scroll the content. -// The provided buttons must be unique, e.g. the same button cannot be both up -// and down. -func ScrollMouseButtons(up, down mouse.Button) Option { - return option(func(opts *options) { - opts.mouseUpButton = up - opts.mouseDownButton = down - }) -} - -// The default keys for content scrolling. -const ( - DefaultScrollKeyUp = keyboard.KeyArrowUp - DefaultScrollKeyDown = keyboard.KeyArrowDown - DefaultScrollKeyPageUp = keyboard.KeyPgUp - DefaultScrollKeyPageDown = keyboard.KeyPgDn -) - -// ScrollKeys configures the keyboard keys that scroll the content. -// The provided keys must be unique, e.g. the same key cannot be both up and -// down. -func ScrollKeys(up, down, pageUp, pageDown keyboard.Key) Option { - return option(func(opts *options) { - opts.keyUp = up - opts.keyDown = down - opts.keyPgUp = pageUp - opts.keyPgDown = pageDown - }) -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/scroll.go b/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/scroll.go deleted file mode 100644 index f63092fdb..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/scroll.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package text - -// scroll.go contains code that tracks the current scrolling position. - -import "math" - -// scrollTracker tracks the current scrolling position for the Text widget. -// -// The text widget displays the contained text buffer as lines of text that fit -// the widget's canvas. The main goal of this object is to inform the text -// widget which should be the first drawn line from the buffer. This depends on -// two things, the scrolling position based on user inputs and whether the text -// widget is configured to roll the content up as new text is added by the -// client. -// -// The rolling Vs. scrolling state is tracked in an FSM implemented in this -// file. -// -// The client can scroll the content by either a keyboard or a mouse event. The -// widget receives these events concurrently with requests to redraw the -// content, so this objects keeps a track of all the scrolling events that -// happened since the last redraw and consumes them when calculating which is -// the first drawn line on the next redraw event. -// -// This is not thread safe. -type scrollTracker struct { - // scroll stores user requests to scroll up (negative) or down (positive). - // E.g. -1 means up by one line and 2 means down by two lines. - scroll int - - // scrollPage stores user requests to scroll up (negative) or down - // (positive) by a page of content. E.g. -1 means up by one page and 2 - // means down by two pages. - scrollPage int - - // first tracks the first line that will be printed. - first int - - // state is the state of the scrolling FSM. - state rollState -} - -// newScrollTracker returns a new scroll tracker. -func newScrollTracker(opts *options) *scrollTracker { - if opts.rollContent { - return &scrollTracker{state: rollToEnd} - } - return &scrollTracker{state: rollingDisabled} -} - -// upOneLine processes a user request to scroll up by one line. -func (st *scrollTracker) upOneLine() { - st.scroll-- -} - -// downOneLine processes a user request to scroll down by one line. -func (st *scrollTracker) downOneLine() { - st.scroll++ -} - -// upOnePage processes a user request to scroll up by one page. -func (st *scrollTracker) upOnePage() { - st.scrollPage-- -} - -// downOnePage processes a user request to scroll down by one page. -func (st *scrollTracker) downOnePage() { - st.scrollPage++ -} - -// doScroll processes any outstanding scroll requests and calculates the -// resulting first line. -func (st *scrollTracker) doScroll(lines, height int) int { - first := st.first + st.scroll + st.scrollPage*height - st.scroll = 0 - st.scrollPage = 0 - return normalizeScroll(first, lines, height) -} - -// firstLine returns the number of the first line that should be drawn on a -// canvas of the specified height if there is the provided number of lines of -// text. -func (st *scrollTracker) firstLine(lines, height int) int { - // Execute the scrolling FSM. - st.state = st.state(st, lines, height) - return st.first -} - -// rollState is a state in the scrolling FSM. -type rollState func(st *scrollTracker, lines, height int) rollState - -// rollingDisabled is a state where content rolling was disabled by the -// configuration of the Text widget. -func rollingDisabled(st *scrollTracker, lines, height int) rollState { - st.first = st.doScroll(lines, height) - return rollingDisabled -} - -// rollToEnd is a state in which the last line of the content is always -// visible. When new content arrives, it is rolled upwards. -func rollToEnd(st *scrollTracker, lines, height int) rollState { - // If the user didn't scroll, just roll the content so that the last line - // is visible. - if st.scroll == 0 && st.scrollPage == 0 { - st.first = normalizeScroll(math.MaxInt32, lines, height) - return rollToEnd - } - - st.first = st.doScroll(lines, height) - if lastLineVisible(st.first, lines, height) { - return rollToEnd - } - return rollingPaused -} - -// rollingPaused is a state in which the user scrolled up and made the last -// line scroll out of the view, so the content rolling is paused. -func rollingPaused(st *scrollTracker, lines, height int) rollState { - st.first = st.doScroll(lines, height) - if lastLineVisible(st.first, lines, height) { - return rollToEnd - } - return rollingPaused -} - -// lastLineVisible returns true if the last text line from within the buffer of -// the text widget is visible on the canvas when drawing of the text starts -// from the specified start line, there is the provided total amount of lines -// and the canvas has the height. -func lastLineVisible(start, lines, height int) bool { - return lines-start <= height -} - -// normalizeScroll returns normalized position of the first line that should be -// drawn when drawing the specified number of lines on a canvas with the -// provided height. -func normalizeScroll(first, lines, height int) int { - if first < 0 || lines <= 0 || height <= 0 { - return 0 - } - - if lines <= height { - return 0 // Scrolling not necessary if the content fits. - } - - max := lines - height - if first > max { - return max - } - return first -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/text.go b/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/text.go deleted file mode 100644 index 0712cbcbe..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/text.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package text contains a widget that displays textual data. -package text - -import ( - "fmt" - "image" - "sync" - - "github.com/mum4k/termdash/private/canvas" - "github.com/mum4k/termdash/private/canvas/buffer" - "github.com/mum4k/termdash/private/wrap" - "github.com/mum4k/termdash/terminal/terminalapi" - "github.com/mum4k/termdash/widgetapi" -) - -// Text displays a block of text. -// -// Each line of the text is either trimmed or wrapped according to the provided -// options. The entire text content is either trimmed or rolled up through the -// canvas according to the provided options. -// -// By default the widget supports scrolling of content with either the keyboard -// or mouse. See the options for the default keys and mouse buttons. -// -// Implements widgetapi.Widget. This object is thread-safe. -type Text struct { - // content is the text content that will be displayed in the widget as - // provided by the caller (i.e. not wrapped or pre-processed). - content []*buffer.Cell - // wrapped is the content wrapped to the current width of the canvas. - wrapped [][]*buffer.Cell - - // scroll tracks scrolling the position. - scroll *scrollTracker - - // lastWidth stores the width of the last canvas the widget drew on. - // Used to determine if the previous line wrapping was invalidated. - lastWidth int - // contentChanged indicates if the text content of the widget changed since - // the last drawing. Used to determine if the previous line wrapping was - // invalidated. - contentChanged bool - - // mu protects the Text widget. - mu sync.Mutex - - // opts are the provided options. - opts *options -} - -// New returns a new text widget. -func New(opts ...Option) (*Text, error) { - opt := newOptions(opts...) - if err := opt.validate(); err != nil { - return nil, err - } - return &Text{ - scroll: newScrollTracker(opt), - opts: opt, - }, nil -} - -// Reset resets the widget back to empty content. -func (t *Text) Reset() { - t.mu.Lock() - defer t.mu.Unlock() - t.reset() -} - -// reset implements Reset, caller must hold t.mu. -func (t *Text) reset() { - t.content = nil - t.wrapped = nil - t.scroll = newScrollTracker(t.opts) - t.lastWidth = 0 - t.contentChanged = true -} - -// Write writes text for the widget to display. Multiple calls append -// additional text. The text contain cannot control characters -// (unicode.IsControl) or space character (unicode.IsSpace) other than: -// ' ', '\n' -// Any newline ('\n') characters are interpreted as newlines when displaying -// the text. -func (t *Text) Write(text string, wOpts ...WriteOption) error { - t.mu.Lock() - defer t.mu.Unlock() - - if err := wrap.ValidText(text); err != nil { - return err - } - - opts := newWriteOptions(wOpts...) - if opts.replace { - t.reset() - } - for _, r := range text { - t.content = append(t.content, buffer.NewCell(r, opts.cellOpts)) - } - t.contentChanged = true - return nil -} - -// minLinesForMarkers are the minimum amount of lines required on the canvas in -// order to draw the scroll markers ('⇧' and '⇩'). -const minLinesForMarkers = 3 - -// drawScrollUp draws the scroll up marker on the first line if there is more -// text "above" the canvas due to the scrolling position. Returns true if the -// marker was drawn. -func (t *Text) drawScrollUp(cvs *canvas.Canvas, cur image.Point, fromLine int) (bool, error) { - height := cvs.Area().Dy() - if cur.Y == 0 && height >= minLinesForMarkers && fromLine > 0 { - cells, err := cvs.SetCell(cur, '⇧') - if err != nil { - return false, err - } - if cells != 1 { - panic(fmt.Errorf("invalid scroll up marker, it occupies %d cells, the implementation only supports scroll markers that occupy exactly one cell", cells)) - } - return true, nil - } - return false, nil -} - -// drawScrollDown draws the scroll down marker on the last line if there is -// more text "below" the canvas due to the scrolling position. Returns true if -// the marker was drawn. -func (t *Text) drawScrollDown(cvs *canvas.Canvas, cur image.Point, fromLine int) (bool, error) { - height := cvs.Area().Dy() - lines := len(t.wrapped) - if cur.Y == height-1 && height >= minLinesForMarkers && height < lines-fromLine { - cells, err := cvs.SetCell(cur, '⇩') - if err != nil { - return false, err - } - if cells != 1 { - panic(fmt.Errorf("invalid scroll down marker, it occupies %d cells, the implementation only supports scroll markers that occupy exactly one cell", cells)) - } - return true, nil - } - return false, nil -} - -// draw draws the text context on the canvas starting at the specified line. -func (t *Text) draw(cvs *canvas.Canvas) error { - var cur image.Point // Tracks the current drawing position on the canvas. - height := cvs.Area().Dy() - fromLine := t.scroll.firstLine(len(t.wrapped), height) - - for _, line := range t.wrapped[fromLine:] { - // Scroll up marker. - scrlUp, err := t.drawScrollUp(cvs, cur, fromLine) - if err != nil { - return err - } - if scrlUp { - cur = image.Point{0, cur.Y + 1} // Move to the next line. - // Skip one line of text, the marker replaced it. - continue - } - - // Scroll down marker. - scrlDown, err := t.drawScrollDown(cvs, cur, fromLine) - if err != nil { - return err - } - if scrlDown || cur.Y >= height { - break // Skip all lines falling after (under) the canvas. - } - - for _, cell := range line { - tr, err := lineTrim(cvs, cur, cell.Rune, t.opts) - if err != nil { - return err - } - cur = tr.curPoint - if tr.trimmed { - break // Skip over any characters trimmed on the current line. - } - - cells, err := cvs.SetCell(cur, cell.Rune, cell.Opts) - if err != nil { - return err - } - cur = image.Point{cur.X + cells, cur.Y} // Move within the same line. - } - cur = image.Point{0, cur.Y + 1} // Move to the next line. - } - return nil -} - -// Draw draws the text onto the canvas. -// Implements widgetapi.Widget.Draw. -func (t *Text) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error { - t.mu.Lock() - defer t.mu.Unlock() - - width := cvs.Area().Dx() - if len(t.content) > 0 && (t.contentChanged || t.lastWidth != width) { - // The previous text preprocessing (line wrapping) is invalidated when - // new text is added or the width of the canvas changed. - wr, err := wrap.Cells(t.content, width, t.opts.wrapMode) - if err != nil { - return err - } - t.wrapped = wr - } - t.lastWidth = width - - if len(t.wrapped) == 0 { - return nil // Nothing to draw if there's no text. - } - - if err := t.draw(cvs); err != nil { - return err - } - t.contentChanged = false - return nil -} - -// Keyboard implements widgetapi.Widget.Keyboard. -func (t *Text) Keyboard(k *terminalapi.Keyboard) error { - t.mu.Lock() - defer t.mu.Unlock() - - switch { - case k.Key == t.opts.keyUp: - t.scroll.upOneLine() - case k.Key == t.opts.keyDown: - t.scroll.downOneLine() - case k.Key == t.opts.keyPgUp: - t.scroll.upOnePage() - case k.Key == t.opts.keyPgDown: - t.scroll.downOnePage() - } - return nil -} - -// Mouse implements widgetapi.Widget.Mouse. -func (t *Text) Mouse(m *terminalapi.Mouse) error { - t.mu.Lock() - defer t.mu.Unlock() - - switch b := m.Button; { - case b == t.opts.mouseUpButton: - t.scroll.upOneLine() - case b == t.opts.mouseDownButton: - t.scroll.downOneLine() - } - return nil -} - -// Options of the widget -func (t *Text) Options() widgetapi.Options { - var ks widgetapi.KeyScope - var ms widgetapi.MouseScope - if t.opts.disableScrolling { - ks = widgetapi.KeyScopeNone - ms = widgetapi.MouseScopeNone - } else { - ks = widgetapi.KeyScopeFocused - ms = widgetapi.MouseScopeWidget - } - - return widgetapi.Options{ - // At least one line with at least one full-width rune. - MinimumSize: image.Point{1, 1}, - WantMouse: ms, - WantKeyboard: ks, - } -} diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/write_options.go b/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/write_options.go deleted file mode 100644 index ddb5c40bc..000000000 --- a/examples/go-dashboard/src/github.com/mum4k/termdash/widgets/text/write_options.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package text - -// write_options.go contains options used when writing content to the Text widget. - -import ( - "github.com/mum4k/termdash/cell" -) - -// WriteOption is used to provide options to Write(). -type WriteOption interface { - // set sets the provided option. - set(*writeOptions) -} - -// writeOptions stores the provided options. -type writeOptions struct { - cellOpts *cell.Options - replace bool -} - -// newWriteOptions returns new writeOptions instance. -func newWriteOptions(wOpts ...WriteOption) *writeOptions { - wo := &writeOptions{ - cellOpts: cell.NewOptions(), - } - for _, o := range wOpts { - o.set(wo) - } - return wo -} - -// writeOption implements WriteOption. -type writeOption func(*writeOptions) - -// set implements WriteOption.set. -func (wo writeOption) set(wOpts *writeOptions) { - wo(wOpts) -} - -// WriteCellOpts sets options on the cells that contain the text. -func WriteCellOpts(opts ...cell.Option) WriteOption { - return writeOption(func(wOpts *writeOptions) { - wOpts.cellOpts = cell.NewOptions(opts...) - }) -} - -// WriteReplace instructs the text widget to replace the entire text content on -// this write instead of appending. -func WriteReplace() WriteOption { - return writeOption(func(wOpts *writeOptions) { - wOpts.replace = true - }) -} |