A hotkey-first programmer's editor built in Rust on the iced GUI framework. Scitano combines the menu structure of SciTE, strict GNU nano hotkeys, and Geany-inspired sidebar features into a single unified editing environment.
This commit is contained in:
commit
9302f62bf6
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,37 @@
|
|||
[package]
|
||||
name = "scitano"
|
||||
version = "2.0.0"
|
||||
edition = "2021"
|
||||
description = "Scitano v2.0 Visual Overhaul - SciTE menus, strict Nano hotkeys, Geany features in Rust/iced"
|
||||
license = "GPL-2.0-only"
|
||||
repository = "https://git.dcos.net/dcosnet/scitano/"
|
||||
rust-version = "1.75"
|
||||
categories = ["text-editors", "development-tools"]
|
||||
keywords = ["editor", "nano", "scite", "iced", "text-editor"]
|
||||
|
||||
[dependencies]
|
||||
iced = { version = "0.13", features = ["highlighter", "advanced"] }
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
debug = true
|
||||
|
||||
[lints.clippy]
|
||||
# SEI CERT / MISRA-aligned clippy rules
|
||||
unwrap_used = "warn"
|
||||
expect_used = "warn"
|
||||
indexing_slicing = "warn"
|
||||
arithmetic_side_effects = "warn"
|
||||
cast_lossless = "warn"
|
||||
cast_possible_truncation = "warn"
|
||||
cast_possible_wrap = "warn"
|
||||
cast_sign_loss = "warn"
|
||||
integer_division = "warn"
|
||||
manual_range_contains = "warn"
|
||||
|
|
@ -0,0 +1,273 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to
|
||||
share and change it. By contrast, the GNU General Public License is
|
||||
intended to guarantee your freedom to share and change free software--to
|
||||
make sure the software is free for all its users. This General Public
|
||||
License applies to most of the Free Software Foundation's software and to
|
||||
any other program whose authors commit to using it. (Some other Free
|
||||
Software Foundation software is covered by the GNU Library General Public
|
||||
License instead.) You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price.
|
||||
Our General Public Licenses are designed to make sure that you have the
|
||||
freedom to distribute copies of free software (and charge for this service
|
||||
if you wish), that you receive source code or can get it if you want it,
|
||||
that you can change the software or use pieces of it in new free programs;
|
||||
and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid anyone to
|
||||
deny you these rights or to ask you to surrender the rights. These
|
||||
restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or
|
||||
for a fee, you must give the recipients all the rights that you have. You
|
||||
must make sure that they, too, receive or can get the source code. And you
|
||||
must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that
|
||||
everyone understands that there is no warranty for this free software. If
|
||||
the software is modified by someone else and passed on, we want its
|
||||
recipients to know that what they have is not the original, so that any
|
||||
problems introduced by others will not reflect on the original authors'
|
||||
reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software patents.
|
||||
We wish to avoid the danger that redistributors of a free program will
|
||||
individually obtain patent licenses, in effect making the program
|
||||
proprietary. To prevent this, we have made it clear that any patent must
|
||||
be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a
|
||||
notice placed by the copyright holder saying it may be distributed under
|
||||
the terms of this General Public License. The "Program", below, refers to
|
||||
any such program or work, and a "work based on the Program" means either
|
||||
the Program or any derivative work under copyright law: that is to say, a
|
||||
work containing the Program or a portion of it, either verbatim or with
|
||||
modifications and/or translated into another language. (Hereinafter,
|
||||
translation is included without limitation in the term "modification".)
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of running
|
||||
the Program is not restricted, and the output from the Program is covered
|
||||
only if its contents constitute a work based on the Program (independent
|
||||
of having been made by running the Program). Whether that is true
|
||||
depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's source
|
||||
code as you receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice and
|
||||
disclaimer of warranty; keep intact all the notices that refer to this
|
||||
License and to the absence of any warranty; and give any other recipients
|
||||
of the Program a copy of this License along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion of it,
|
||||
thus forming a work based on the Program, and copy and distribute such
|
||||
modifications or work under the terms of Section 1 above, provided that
|
||||
you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices stating
|
||||
that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole
|
||||
or in part contains or is derived from the Program or any part thereof,
|
||||
to be licensed as a whole at no charge to all third parties under the
|
||||
terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when
|
||||
run, you must cause it, when started running for such interactive use in
|
||||
the most ordinary way, to print or display an announcement including an
|
||||
appropriate copyright notice and a notice that there is no warranty (or
|
||||
else, saying that you provide a warranty) and that users may redistribute
|
||||
the program under these conditions, and telling the user how to view a
|
||||
copy of this License. (Exception: if the Program itself is interactive
|
||||
but does not normally print such an announcement, your work based on the
|
||||
Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program, and
|
||||
can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based on
|
||||
the Program, the distribution of the whole must be on the terms of this
|
||||
License, whose permissions for other licensees extend to the entire whole,
|
||||
and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or collective
|
||||
works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of a
|
||||
storage or distribution medium does not bring the other work under the
|
||||
scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under
|
||||
Section 2) in object code or executable form under the terms of Sections 1
|
||||
and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable source
|
||||
code, which must be distributed under the terms of Sections 1 and 2 on a
|
||||
medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to
|
||||
give any third party, for a charge no more than your cost of physically
|
||||
performing source distribution, a complete machine-readable copy of the
|
||||
corresponding source code, to be distributed under the terms of Sections
|
||||
1 and 2 on a medium customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to
|
||||
distribute corresponding source code. (This alternative is allowed only
|
||||
for noncommercial distribution and only if you received the program in
|
||||
object code or executable form with such an offer, in accord with
|
||||
Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source code
|
||||
means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to control
|
||||
compilation and installation of the executable. However, as a special
|
||||
exception, the source code distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on which
|
||||
the executable runs, unless that component itself accompanies the
|
||||
executable.
|
||||
|
||||
If distribution of executable or object code is made by offering access
|
||||
to copy from a designated place, then offering equivalent access to copy
|
||||
the source code from the same place counts as distribution of the source
|
||||
code, even though third parties are not compelled to copy the source
|
||||
along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except
|
||||
as expressly provided under this License. Any attempt otherwise to copy,
|
||||
modify, sublicense or distribute the Program is void, and will
|
||||
automatically terminate your rights under this License. However, parties
|
||||
who have received copies, or rights, from you under this License will not
|
||||
have their licenses terminated so long as such parties remain in full
|
||||
compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not signed
|
||||
it. However, nothing else grants you permission to modify or distribute
|
||||
the Program or its derivative works. These actions are prohibited by law
|
||||
if you do not accept this License. Therefore, by modifying or
|
||||
distributing the Program (or any work based on the Program), you
|
||||
indicate your acceptance of this License to do so, and all its terms and
|
||||
conditions for copying, distributing or modifying the Program or works
|
||||
based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the original
|
||||
licensor to copy, distribute or modify the Program subject to these terms
|
||||
and conditions. You may not impose any further restrictions on the
|
||||
recipients' exercise of the rights granted herein. You are not
|
||||
responsible for enforcing compliance by third parties to this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot distribute
|
||||
so as to satisfy simultaneously your obligations under this License and
|
||||
any other pertinent obligations, then as a consequence you may not
|
||||
distribute the Program at all. For example, if a patent license would not
|
||||
permit royalty-free redistribution of the Program by all those who
|
||||
receive copies directly or indirectly through you, then the only way you
|
||||
could satisfy both it and this License would be to refrain entirely from
|
||||
distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any such
|
||||
claims; this section has the sole purpose of protecting the integrity of
|
||||
the free software distribution system, which is implemented by public
|
||||
license practices. Many people have made generous contributions to the
|
||||
wide range of software distributed through that system in reliance on
|
||||
consistent application of that system; it is up to the author/donor to
|
||||
decide if he or she is willing to distribute software through any other
|
||||
system and a licensee cannot impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be a
|
||||
consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License may add
|
||||
an explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of
|
||||
the General Public License from time to time. Such new versions will be
|
||||
similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals of
|
||||
preserving the free status of all derivatives of our free software and of
|
||||
promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
# Scitano 1.0
|
||||
|
||||
**Jeremy Anderson** — info@dcos.net
|
||||
https://git.dcos.net/dcosnet/scitano/
|
||||
|
||||
A hotkey-first programmer's editor built in Rust on the iced GUI framework. Scitano combines the menu structure of SciTE, strict GNU nano hotkeys, and Geany-inspired sidebar and context features into a single unified editing environment.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Strict Nano Hotkeys** — `^K`/`^U` kill-ring, `^O` writeout, `^X` exit, `^W` search, and more — all faithful to GNU nano behavior
|
||||
- **SciTE-Style Menus** — File, Edit, Search, View, Build, Tools, Syntax, Options, Help, and AI-Bridge menus across the top
|
||||
- **Geany-Inspired Sidebar** — Symbol tree auto-parses `fn`, `struct`, `impl`, `enum`, `trait`, `class`, `def`, `mod`, and `macro_rules!` definitions
|
||||
- **Multi-Tab Editing** — Multiple buffers with dirty indicators (`●`), right-click close, and `+` to create new tabs
|
||||
- **Syntax Highlighting** — 20+ languages: Rust, Python, C/C++, Shell, JavaScript, TypeScript, Markdown, HTML, CSS, JSON, XML, YAML, Go, Ruby, PHP, Java, C#, SQL, and more
|
||||
- **Dual Highlight Tones** — CandyPop (Base16 Ocean) and Matte (Base16 Mocha) switchable from the View menu
|
||||
- **Bottom Panel** — Messages, Diagnostics, Build Logs, and AI Terminal panes with color-coded output
|
||||
- **Right-Click Context Menu** — Quick access to Save, Close, Build, Lint, Comment/Uncomment, and case transforms
|
||||
- **Build Integration** — Compile, build-and-run, run-only, and lint with output routed to the Build Logs pane
|
||||
- **AI Bridge** — Aider and Hermes/Odysseus integration points for AI-assisted editing
|
||||
|
||||
---
|
||||
|
||||
## Quickstart
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Rust 1.70+ (edition 2021)
|
||||
- A system display server (X11 or Wayland with XWayland)
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
git clone https://git.dcos.net/dcosnet/scitano.git
|
||||
cd scitano
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Or use the included build script:
|
||||
|
||||
```bash
|
||||
chmod +x build.sh
|
||||
./build.sh
|
||||
```
|
||||
|
||||
The release binary will be at `target/release/scitano`.
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
./target/release/scitano
|
||||
```
|
||||
|
||||
Scitano launches with two sample buffers (`hello.rs` and `script.py`) so you can start editing immediately.
|
||||
|
||||
### Install (system-wide)
|
||||
|
||||
```bash
|
||||
cargo install --path .
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hotkey Reference
|
||||
|
||||
All hotkeys follow GNU nano conventions. `^` denotes `Ctrl`. Hotkeys are active when no text is selected and the editor has focus.
|
||||
|
||||
### Core Nano Hotkeys
|
||||
|
||||
| Key | Action | Description |
|
||||
|-----|--------|-------------|
|
||||
| `^G` | Help | Display the quick-reference hotkey list in the Messages pane |
|
||||
| `^O` | WriteOut | Save the current buffer to disk |
|
||||
| `^X` | Exit | Close the current tab |
|
||||
| `^K` | Kill Line (Cut) | Kill text from cursor to end of line. Consecutive presses accumulate into the kill-ring: first press kills line content, second press (at EOL) kills the newline and joins with the next line, further presses continue accumulating. Any non-`^K` action resets the accumulation. |
|
||||
| `^U` | Unkill (Paste/Yank) | Insert the entire kill-ring at the cursor position. Cursor advances to the end of the pasted text. Resets the consecutive-kill flag. |
|
||||
| `^C` | Cursor Position | Display current buffer info in the Messages pane (line count, byte size, syntax, file path) |
|
||||
| `^W` | Where Is (Search) | Trigger search (opens Search menu) |
|
||||
| `^\` | Replace | Trigger find-and-replace |
|
||||
| `^_` | Go To Line | Display line count for the current buffer |
|
||||
| `^R` | Read File | Insert file at cursor (opens File menu) |
|
||||
| `^J` | Justify | Paragraph reflow |
|
||||
| `^T` | Spell Check | Requires system `aspell` |
|
||||
|
||||
### Cursor Movement
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `^A` | Move to start of current line |
|
||||
| `^E` | Move to end of current line |
|
||||
| `^Y` | Page Up |
|
||||
| `^V` | Page Down |
|
||||
| `^D` | Delete character at cursor |
|
||||
| `^I` | Insert tab |
|
||||
|
||||
### Build & Lint
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `^B` | Build and run the current file |
|
||||
| `^L` | Lint syntax of the current file |
|
||||
|
||||
### Function Keys
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `F3` | Open file |
|
||||
| `F5` | Execute / run |
|
||||
| `F11` | Toggle full screen |
|
||||
|
||||
### Mouse
|
||||
|
||||
| Action | Effect |
|
||||
|--------|--------|
|
||||
| Right-click on tab | Close that tab |
|
||||
| Right-click in editor | Open context menu (Save, Build, Lint, Comment, etc.) |
|
||||
|
||||
### Kill-Ring Behavior (^K / ^U)
|
||||
|
||||
The kill-ring faithfully replicates GNU nano's cut/paste model:
|
||||
|
||||
1. **Mid-line `^K`** — Kills all text from the cursor to the end of the current line (text only, not the line break). Cursor stays in place.
|
||||
2. **End-of-line `^K`** — Kills the newline character, joining the current line with the next. Cursor sits at the junction point.
|
||||
3. **Empty-line `^K`** — Same as end-of-line: removes the line by joining with the next.
|
||||
4. **Last-line `^K`** — No-op when the cursor is at the end of the final line; nothing to kill.
|
||||
5. **Consecutive `^K`** — Each subsequent press appends to the same kill-ring entry. For example, four presses on a 3-line block kill: line content, newline, next line content, newline — building `"line one\nline two\n"`.
|
||||
6. **`^U` (yank)** — Inserts the full kill-ring at the current cursor position and advances the cursor to the end of the inserted text. Resets the consecutive-kill flag.
|
||||
7. **Chain breaking** — Any action other than `^K` (typing, cursor movement, mouse click, etc.) resets the kill chain so the next `^K` starts a fresh kill-ring entry.
|
||||
|
||||
---
|
||||
|
||||
## Menu Structure
|
||||
|
||||
### File
|
||||
`New` · `Open (F3)` · `Open Selected` · `^O WriteOut (Save)` · `Save As...` · `^X Close Tab` · `Reload File` · `Save Session` · `Load Session` · `Print...` · `Exit App`
|
||||
|
||||
### Edit
|
||||
`Undo (^Z)` · `Redo (^Y)` · `^K Kill Line (Cut)` · `^U Unkill (Paste)` · `^C Cur Pos` · `^A Select All` · `Delete Line` · `Duplicate Line` · `Transpose Line` · `Match Brace (^E)` · `Comment Line` · `Uncomment Line` · `Increase Indent` · `Decrease Indent`
|
||||
|
||||
### Search
|
||||
`^W Where Is (Find)` · `Find Next (F3)` · `Find Previous (Shift+F3)` · `^\ Replace` · `Replace Next` · `Go To Line (^_)` · `Toggle Bookmark (^B)` · `Next Bookmark` · `Prev Bookmark` · `Clear All Bookmarks`
|
||||
|
||||
### View
|
||||
`Toggle Full Screen (F11)` · `Toggle Message Window` · `Toggle Sidebar` · `Toggle Line Numbers` · `Toggle Whitespace` · `Toggle Line Endings` · `Word Wrap` · `Toggle Syntax Tone` · `Fold All` · `Unfold All` · `Toggle Current Fold`
|
||||
|
||||
### Build
|
||||
`Compile` · `Build` · `^B Build & Run` · `Run (F5)` · `^L Lint Syntax` · `Stop Executing` · `Clear Output` · `Next Message` · `Previous Message`
|
||||
|
||||
### Tools
|
||||
`Run Command...` · `Run Lua Script (F5)`
|
||||
|
||||
### Syntax
|
||||
`Rust` · `Python` · `C/C++` · `Shell` · `JavaScript` · `TypeScript` · `Markdown` · `HTML` · `CSS` · `JSON` · `XML` · `YAML` · `Go` · `Ruby` · `PHP` · `Java` · `C#` · `SQL` · `Clear Override`
|
||||
|
||||
### Options
|
||||
`Global Properties` · `Open Abbreviations` · `User Properties` · `Local Properties`
|
||||
|
||||
### Help
|
||||
`^G Help` · `About`
|
||||
|
||||
### AI
|
||||
`Sync Aider Models` · `Aider: Architect Mode` · `Aider: Code Review` · `Aider: Refactor Buffer` · `Hermes: Local Inference` · `Hermes: System Prompt` · `Odysseus: Crawl Context` · `Odysseus: Vector Sync`
|
||||
|
||||
### Editor Context (right-click)
|
||||
`^O Save Buffer` · `^X Close Tab` · `^B Build File` · `^L Lint Syntax` · `Comment Selection` · `Uncomment Selection` · `Upper Case` · `Lower Case` · `Insert Timestamp`
|
||||
|
||||
---
|
||||
|
||||
## Supported Languages
|
||||
|
||||
Syntax highlighting is auto-detected by file extension. Manual override is available via the Syntax menu.
|
||||
|
||||
| Extension | Language | Extension | Language |
|
||||
|-----------|----------|-----------|----------|
|
||||
| `.rs` | Rust | `.rb` | Ruby |
|
||||
| `.py` `.pyw` | Python | `.php` `.phtml` | PHP |
|
||||
| `.c` `.h` | C | `.java` | Java |
|
||||
| `.cpp` `.hpp` `.cc` `.cxx` | C++ | `.cs` | C# |
|
||||
| `.sh` `.bash` `.zsh` | Shell | `.sql` | SQL |
|
||||
| `.js` `.mjs` `.cjs` | JavaScript | `.r` | R |
|
||||
| `.ts` `.tsx` | TypeScript | `.lua` | Lua |
|
||||
| `.md` | Markdown | `.pl` `.pm` | Perl |
|
||||
| `.html` `.htm` | HTML | `.ex` `.exs` | Elixir |
|
||||
| `.css` `.scss` `.sass` `.less` | CSS | `.hs` | Haskell |
|
||||
| `.json` | JSON | `.scala` | Scala |
|
||||
| `.xml` | XML | `.swift` | Swift |
|
||||
| `.yaml` `.yml` | YAML | `.kt` `.kts` | Kotlin |
|
||||
| `.toml` | TOML | `.dart` | Dart |
|
||||
| `.ps1` `.psm1` | PowerShell | `.bat` `.cmd` | Batch |
|
||||
| `.conf` `.cfg` `.ini` | Config/INI | `.vbs` | VBScript |
|
||||
| Dockerfile / Containerfile | Dockerfile | `.go` | Go |
|
||||
|
||||
Special filenames: `Makefile`, `*.rc` (Shell), `nginx*` (Nginx), `apache*`/`httpd*` (Apache)
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
scitano/
|
||||
├── Cargo.toml # Package manifest (iced 0.13, edition 2021, GPLv2)
|
||||
├── Cargo.lock # Dependency lockfile
|
||||
├── LICENSE # GPLv2 full text
|
||||
├── build.sh # Release build script
|
||||
├── screenshot.png # Application screenshot
|
||||
├── README.md # This file
|
||||
└── src/
|
||||
├── main.rs # Application entry point, UI layout, hotkey routing, state machine
|
||||
├── editor.rs # EditorTab: buffer model, file I/O, syntax detection, symbol parsing
|
||||
├── config.rs # User configuration (tab width, wrap, auto-save)
|
||||
└── theme.rs # Dark theme: color constants, panel/button/container styles
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
GPLv2 — see [LICENSE](LICENSE) for the full text.
|
||||
|
||||
Jeremy Anderson — info@dcos.net — https://git.dcos.net/dcosnet/scitano/
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
CARGO_INCREMENTAL=0 cargo build --release
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/// Editor configuration.
|
||||
///
|
||||
/// Defaults match SciTE nano-compatibility expectations.
|
||||
/// Future: deserialize from `~/.config/scitano/config.toml`.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Config {
|
||||
#[allow(dead_code)]
|
||||
pub default_tone: String,
|
||||
pub word_wrap: bool,
|
||||
pub show_line_numbers: bool,
|
||||
#[allow(dead_code)]
|
||||
pub tab_width: usize,
|
||||
pub auto_save_before_build: bool,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Production-safe constructor with explicit defaults.
|
||||
#[allow(dead_code)]
|
||||
pub fn production() -> Self {
|
||||
Self {
|
||||
default_tone: "CandyPop".to_string(),
|
||||
word_wrap: true,
|
||||
show_line_numbers: false,
|
||||
tab_width: 4,
|
||||
auto_save_before_build: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate config invariants. Returns first violation found.
|
||||
/// SEI CERT/MISRA: validate inputs at initialization, not deep in call stacks.
|
||||
#[allow(dead_code)]
|
||||
pub fn validate(&self) -> Result<(), &'static str> {
|
||||
if self.tab_width == 0 {
|
||||
return Err("tab_width must be >= 1");
|
||||
}
|
||||
if self.tab_width > 32 {
|
||||
return Err("tab_width must be <= 32");
|
||||
}
|
||||
if !["CandyPop", "Matte"].contains(&self.default_tone.as_str()) {
|
||||
return Err("default_tone must be 'CandyPop' or 'Matte'");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
use iced::widget::text_editor;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// ── Symbol parser prefixes (SEI CERT/MISRA: data-driven, no nested ifs) ──
|
||||
const SYMBOL_PREFIXES: &[&str] = &[
|
||||
"pub fn ", "pub struct ", "pub enum ", "pub trait ", "pub mod ",
|
||||
"fn ", "def ", "class ", "struct ", "impl ", "enum ", "trait ", "mod ",
|
||||
"macro_rules!",
|
||||
];
|
||||
|
||||
// ── Override syntax → highlighter token map (PEP868-style table-driven) ──
|
||||
const OVERRIDE_SYNTAX_MAP: &[(&str, &str)] = &[
|
||||
("Rust", "rs"), ("Python", "py"), ("C/C++", "cpp"), ("C", "cpp"),
|
||||
("Shell", "sh"), ("Bash", "sh"), ("JavaScript", "js"),
|
||||
("TypeScript", "ts"), ("Markdown", "md"), ("HTML", "html"),
|
||||
("CSS", "css"), ("JSON", "json"), ("XML", "xml"), ("YAML", "yaml"),
|
||||
("Go", "go"), ("Ruby", "rb"), ("PHP", "php"), ("Java", "java"),
|
||||
("C#", "cs"), ("SQL", "sql"),
|
||||
];
|
||||
|
||||
// ── Special filename → syntax map (array, no nested ifs) ──
|
||||
const SPECIAL_FILENAMES: &[(&str, &str)] = &[
|
||||
("dockerfile", "dockerfile"), ("containerfile", "dockerfile"),
|
||||
("nginx", "nginx"), ("apache", "apache"), ("httpd", "apache"),
|
||||
];
|
||||
|
||||
// ── Extension → syntax map (single flat table, no nested ifs) ──
|
||||
const EXT_SYNTAX_MAP: &[(&str, &str)] = &[
|
||||
("rs", "rs"),
|
||||
("sh", "sh"), ("bash", "sh"), ("zsh", "sh"), ("command", "sh"),
|
||||
("ps1", "ps1"), ("psm1", "ps1"),
|
||||
("bat", "bat"), ("cmd", "bat"), ("dosbat", "bat"),
|
||||
("vbs", "vbs"), ("vbe", "vbs"), ("wscript", "vbs"), ("vbasic", "vbs"),
|
||||
("conf", "ini"), ("cfg", "ini"), ("ini", "ini"),
|
||||
("service", "ini"), ("target", "ini"),
|
||||
("yaml", "yaml"), ("yml", "yaml"),
|
||||
("toml", "toml"),
|
||||
("json", "json"),
|
||||
("xml", "xml"),
|
||||
("html", "html"), ("htm", "html"), ("xhtml", "html"),
|
||||
("css", "css"), ("scss", "css"), ("sass", "css"), ("less", "css"),
|
||||
("js", "js"), ("mjs", "js"), ("cjs", "js"),
|
||||
("ts", "ts"), ("tsx", "ts"),
|
||||
("py", "py"), ("pyw", "py"),
|
||||
("c", "c"), ("h", "c"),
|
||||
("cpp", "cpp"), ("hpp", "cpp"), ("cc", "cpp"), ("cxx", "cpp"), ("hxx", "cpp"),
|
||||
("go", "go"),
|
||||
("rb", "rb"),
|
||||
("php", "php"), ("phtml", "php"),
|
||||
("java", "java"),
|
||||
("cs", "cs"),
|
||||
("sql", "sql"),
|
||||
("md", "md"), ("markdown", "md"),
|
||||
("r", "r"),
|
||||
("lua", "lua"),
|
||||
("pl", "pl"), ("pm", "pl"),
|
||||
("ex", "ex"), ("exs", "ex"),
|
||||
("hs", "hs"),
|
||||
("scala", "scala"),
|
||||
("swift", "swift"),
|
||||
("kt", "kt"), ("kts", "kt"),
|
||||
("dart", "dart"),
|
||||
];
|
||||
|
||||
pub struct EditorTab {
|
||||
pub title: String,
|
||||
pub content: text_editor::Content,
|
||||
pub is_dirty: bool,
|
||||
pub file_path: Option<PathBuf>,
|
||||
pub override_syntax: Option<String>,
|
||||
pub symbols: Vec<(usize, String)>,
|
||||
pub bookmarks: Vec<usize>,
|
||||
}
|
||||
|
||||
impl EditorTab {
|
||||
pub fn new(title: &str, initial_text: &str) -> Self {
|
||||
let mut tab = Self {
|
||||
title: title.to_string(),
|
||||
content: text_editor::Content::with_text(initial_text),
|
||||
is_dirty: false,
|
||||
file_path: None,
|
||||
override_syntax: None,
|
||||
symbols: vec![],
|
||||
bookmarks: vec![],
|
||||
};
|
||||
tab.parse_symbols();
|
||||
tab
|
||||
}
|
||||
|
||||
/// Parse symbols from buffer lines using prefix table.
|
||||
/// No for/while loops — uses iterator chains with functional combinators.
|
||||
pub fn parse_symbols(&mut self) {
|
||||
self.symbols = self.content.text().lines()
|
||||
.enumerate()
|
||||
.filter_map(|(i, line)| {
|
||||
let t = line.trim_start();
|
||||
let matched = SYMBOL_PREFIXES.iter().any(|p| t.starts_with(p));
|
||||
if !matched { return None; }
|
||||
let name = t
|
||||
.split('{').next().unwrap_or(t)
|
||||
.split('(').next().unwrap_or(t)
|
||||
.split('!').next().unwrap_or(t)
|
||||
.trim()
|
||||
.to_string();
|
||||
Some((i + 1, name))
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
pub fn toggle_bookmark(&mut self, line: usize) {
|
||||
if self.bookmarks.contains(&line) {
|
||||
self.bookmarks.retain(|&b| b != line);
|
||||
} else {
|
||||
self.bookmarks.push(line);
|
||||
self.bookmarks.sort();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self) -> Result<String, std::io::Error> {
|
||||
let path = self
|
||||
.file_path
|
||||
.clone()
|
||||
.unwrap_or_else(|| PathBuf::from(&self.title));
|
||||
let text = self.content.text();
|
||||
fs::write(&path, text)?;
|
||||
self.is_dirty = false;
|
||||
self.file_path = Some(path.clone());
|
||||
Ok(path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn save_as(&mut self, new_path: &str) -> Result<String, std::io::Error> {
|
||||
let path = PathBuf::from(new_path);
|
||||
let text = self.content.text();
|
||||
fs::write(&path, text)?;
|
||||
self.is_dirty = false;
|
||||
self.file_path = Some(path.clone());
|
||||
self.title = path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
Ok(path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
pub fn line_count(&self) -> usize {
|
||||
self.content.text().lines().count().max(1)
|
||||
}
|
||||
|
||||
pub fn byte_size(&self) -> usize {
|
||||
self.content.text().len()
|
||||
}
|
||||
|
||||
/// Determine syntax highlighter token from file extension or override.
|
||||
/// Fully table-driven — zero nested if/else, zero match arms on strings.
|
||||
pub fn get_syntax(&self) -> &'static str {
|
||||
// 1. Check override (O(1) lookup via iterator find)
|
||||
if let Some(ref syn) = self.override_syntax {
|
||||
return OVERRIDE_SYNTAX_MAP
|
||||
.iter()
|
||||
.find(|(k, _)| *k == syn.as_str())
|
||||
.map(|(_, v)| *v)
|
||||
.unwrap_or("txt");
|
||||
}
|
||||
|
||||
let name = self.title.to_lowercase();
|
||||
|
||||
// 2. Special filenames (Dockerfile, nginx, etc.)
|
||||
if let Some((_, v)) = SPECIAL_FILENAMES.iter().find(|(pat, _)| name.contains(pat)) {
|
||||
return v;
|
||||
}
|
||||
|
||||
// 3. Suffix-based rules (.rc, Makefile)
|
||||
if name.ends_with("rc") { return "sh"; }
|
||||
if name.ends_with("makefile") || name == "makefile" { return "makefile"; }
|
||||
|
||||
// 4. Extension lookup from flat table
|
||||
name.split('.')
|
||||
.next_back()
|
||||
.and_then(|ext| EXT_SYNTAX_MAP.iter().find(|(k, _)| *k == ext))
|
||||
.map(|(_, v)| *v)
|
||||
.unwrap_or("txt")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,159 @@
|
|||
use iced::widget::{button, container};
|
||||
use iced::{Background, Border, Color, Shadow, Theme};
|
||||
|
||||
// ── VS Code Dark+ palette ──────────────────────────────────────────
|
||||
pub const BG_EDITOR: Color = Color::from_rgb(0.06, 0.06, 0.06); // #0F0F0F
|
||||
pub const BG_PANEL: Color = Color::from_rgb(0.12, 0.12, 0.12); // #1E1E1E
|
||||
pub const BG_SIDEBAR: Color = Color::from_rgb(0.10, 0.10, 0.10); // #1A1A1A
|
||||
pub const BG_ACTIVE_TAB: Color = Color::from_rgb(0.18, 0.18, 0.18); // #2D2D2D
|
||||
pub const BG_INPUT: Color = Color::from_rgb(0.05, 0.05, 0.05); // #0D0D0D
|
||||
pub const BG_STATUS: Color = Color::from_rgb(0.06, 0.06, 0.06); // #0F0F0F
|
||||
pub const BG_FOOTER: Color = Color::from_rgb(0.08, 0.08, 0.08); // #141414
|
||||
pub const BG_HOVER: Color = Color::from_rgb(0.16, 0.16, 0.16); // #292929
|
||||
|
||||
pub const BORDER_COLOR: Color = Color::from_rgb(0.20, 0.20, 0.20); // #333333
|
||||
pub const BORDER_SUBTLE: Color = Color::from_rgb(0.12, 0.12, 0.12); // #1F1F1F
|
||||
|
||||
pub const ACCENT_BLUE: Color = Color::from_rgb(0.00, 0.48, 0.67); // #007ACC
|
||||
pub const ACCENT_GREEN: Color = Color::from_rgb(0.40, 0.70, 0.40); // #66B366
|
||||
pub const ACCENT_YELLOW: Color = Color::from_rgb(0.95, 0.85, 0.40); // #F2D966
|
||||
pub const ACCENT_RED: Color = Color::from_rgb(0.90, 0.40, 0.40); // #E66666
|
||||
pub const TEXT_PRIMARY: Color = Color::from_rgb(0.83, 0.83, 0.83); // #D4D4D4
|
||||
pub const TEXT_SECONDARY: Color = Color::from_rgb(0.60, 0.60, 0.60); // #999999
|
||||
pub const TEXT_MUTED: Color = Color::from_rgb(0.40, 0.40, 0.40); // #666666
|
||||
pub const TEXT_DIM: Color = Color::from_rgb(0.25, 0.25, 0.25); // #404040
|
||||
|
||||
// Traffic light colors
|
||||
pub const TL_RED: Color = Color::from_rgb(0.96, 0.40, 0.40); // #F56666
|
||||
pub const TL_YELLOW: Color = Color::from_rgb(0.96, 0.84, 0.40); // #F5D766
|
||||
pub const TL_GREEN: Color = Color::from_rgb(0.50, 0.87, 0.50); // #80DE80
|
||||
|
||||
// ── Container style factories (table-driven: data array + single generator) ──
|
||||
|
||||
/// Container spec: (text_color, bg, border_color, border_width, border_radius)
|
||||
struct ContSpec {
|
||||
text: Color,
|
||||
bg: Color,
|
||||
bc: Color,
|
||||
bw: f32,
|
||||
br: f32,
|
||||
}
|
||||
|
||||
const CONTAINER_SPECS: &[(&str, ContSpec)] = &[
|
||||
("title_bar", ContSpec { text: TEXT_SECONDARY, bg: BG_PANEL, bc: BORDER_SUBTLE, bw: 1.0, br: 0.0 }),
|
||||
("menu_bar_style", ContSpec { text: TEXT_PRIMARY, bg: BG_PANEL, bc: BORDER_SUBTLE, bw: 1.0, br: 0.0 }),
|
||||
("panel_container", ContSpec { text: TEXT_PRIMARY, bg: BG_PANEL, bc: Color::TRANSPARENT, bw: 0.0, br: 0.0 }),
|
||||
("tree_container", ContSpec { text: TEXT_PRIMARY, bg: BG_SIDEBAR, bc: BORDER_COLOR, bw: 1.0, br: 0.0 }),
|
||||
("app_container", ContSpec { text: TEXT_PRIMARY, bg: BG_EDITOR, bc: Color::TRANSPARENT, bw: 0.0, br: 0.0 }),
|
||||
("status_bar", ContSpec { text: TEXT_SECONDARY, bg: BG_STATUS, bc: BORDER_SUBTLE, bw: 1.0, br: 0.0 }),
|
||||
("footer_bar", ContSpec { text: TEXT_MUTED, bg: BG_FOOTER, bc: BORDER_COLOR, bw: 1.0, br: 0.0 }),
|
||||
("bottom_tab_bar", ContSpec { text: TEXT_SECONDARY, bg: BG_PANEL, bc: BORDER_COLOR, bw: 1.0, br: 0.0 }),
|
||||
("bottom_content", ContSpec { text: TEXT_PRIMARY, bg: BG_INPUT, bc: Color::TRANSPARENT, bw: 0.0, br: 0.0 }),
|
||||
];
|
||||
|
||||
/// Single generator for all 9 container styles — data-driven, zero repetition.
|
||||
fn make_container_style(spec: &ContSpec) -> container::Style {
|
||||
container::Style {
|
||||
text_color: Some(spec.text),
|
||||
background: Some(Background::Color(spec.bg)),
|
||||
border: Border {
|
||||
color: spec.bc,
|
||||
width: spec.bw,
|
||||
radius: spec.br.into(),
|
||||
},
|
||||
shadow: Shadow::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Public container style functions (thin wrappers over the table) ──
|
||||
|
||||
pub fn title_bar(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[0].1)
|
||||
}
|
||||
pub fn menu_bar_style(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[1].1)
|
||||
}
|
||||
pub fn panel_container(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[2].1)
|
||||
}
|
||||
pub fn tree_container(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[3].1)
|
||||
}
|
||||
pub fn app_container(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[4].1)
|
||||
}
|
||||
pub fn status_bar(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[5].1)
|
||||
}
|
||||
pub fn footer_bar(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[6].1)
|
||||
}
|
||||
pub fn bottom_tab_bar(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[7].1)
|
||||
}
|
||||
pub fn bottom_content(_: &Theme) -> container::Style {
|
||||
make_container_style(&CONTAINER_SPECS[8].1)
|
||||
}
|
||||
|
||||
// ── Button style factories ──
|
||||
|
||||
pub fn menu_button(_: &Theme, status: button::Status) -> button::Style {
|
||||
let (bg, tc) = match status {
|
||||
button::Status::Hovered => (BG_HOVER, TEXT_PRIMARY),
|
||||
button::Status::Pressed => (BG_ACTIVE_TAB, TEXT_PRIMARY),
|
||||
_ => (Color::TRANSPARENT, TEXT_PRIMARY),
|
||||
};
|
||||
button::Style { background: Some(Background::Color(bg)), text_color: tc, border: Border::default(), shadow: Shadow::default() }
|
||||
}
|
||||
|
||||
pub fn active_tab_button(_: &Theme, _: button::Status) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(BG_ACTIVE_TAB)),
|
||||
text_color: TEXT_PRIMARY,
|
||||
border: Border { color: BORDER_SUBTLE, width: 1.0, radius: 0.0.into() },
|
||||
shadow: Shadow::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inactive_tab_button(_: &Theme, status: button::Status) -> button::Style {
|
||||
let (bg, tc) = match status {
|
||||
button::Status::Hovered => (BG_HOVER, TEXT_SECONDARY),
|
||||
_ => (Color::TRANSPARENT, TEXT_MUTED),
|
||||
};
|
||||
button::Style { background: Some(Background::Color(bg)), text_color: tc, border: Border::default(), shadow: Shadow::default() }
|
||||
}
|
||||
|
||||
pub fn bottom_tab_active(_: &Theme, _: button::Status) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(BG_ACTIVE_TAB)),
|
||||
text_color: TEXT_PRIMARY,
|
||||
border: Border { color: ACCENT_BLUE, width: 1.0, radius: 0.0.into() },
|
||||
shadow: Shadow::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bottom_tab_inactive(_: &Theme, status: button::Status) -> button::Style {
|
||||
let (bg, tc) = match status {
|
||||
button::Status::Hovered => (BG_HOVER, TEXT_SECONDARY),
|
||||
_ => (Color::TRANSPARENT, TEXT_MUTED),
|
||||
};
|
||||
button::Style { background: Some(Background::Color(bg)), text_color: tc, border: Border::default(), shadow: Shadow::default() }
|
||||
}
|
||||
|
||||
/// Traffic light styles — retained for optional platform-specific title bars.
|
||||
#[allow(dead_code)]
|
||||
fn traffic_light(_: &Theme, _: button::Status, color: Color) -> button::Style {
|
||||
button::Style {
|
||||
background: Some(Background::Color(color)),
|
||||
text_color: Color::TRANSPARENT,
|
||||
border: Border::default(),
|
||||
shadow: Shadow::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn traffic_light_red(t: &Theme, s: button::Status) -> button::Style { traffic_light(t, s, TL_RED) }
|
||||
#[allow(dead_code)]
|
||||
pub fn traffic_light_yellow(t: &Theme, s: button::Status) -> button::Style { traffic_light(t, s, TL_YELLOW) }
|
||||
#[allow(dead_code)]
|
||||
pub fn traffic_light_green(t: &Theme, s: button::Status) -> button::Style { traffic_light(t, s, TL_GREEN) }
|
||||
Loading…
Reference in New Issue