Compare commits

..

82 Commits

Author SHA1 Message Date
Antonio Scandurra
ce45e1cb4c Panic
- Go to diagnostics view
- Select all occurrences of x_for_display_point via cmd-d
- Park the cursors at the start of the parenthesis
- Hit ctrl-m
2024-03-04 20:11:40 +01:00
Antonio Scandurra
78aaaa8b1e WIP 2024-03-04 19:16:29 +01:00
Antonio Scandurra
2045c83099 Move defer_draw to ElementContext 2024-03-04 15:59:25 +01:00
Antonio Scandurra
f57f6344b4 Introduce AnyElement::deferred_draw and use it in Overlay 2024-03-04 14:33:48 +01:00
Antonio Scandurra
022fada0f2 Fix registration of mouse event listeners by using hitbox 2024-03-02 19:19:12 +01:00
Antonio Scandurra
ba45a04045 Move element states for reused views correctly 2024-03-02 14:08:36 +01:00
Antonio Scandurra
f52d9f8c24 Checkpoint
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
2024-03-01 19:15:08 +01:00
Antonio Scandurra
8847f73af5 WIP 2024-03-01 18:21:34 +01:00
Antonio Scandurra
3a7b4de0f5 WIP 2024-03-01 17:12:12 +01:00
Antonio Scandurra
82ec8405d6 WIP 2024-03-01 16:19:33 +01:00
Antonio Scandurra
7b7a046b94 Make occlusion optional in Interactivity 2024-03-01 15:48:54 +01:00
Antonio Scandurra
1ba395a33a Checkpoint
Co-Authored-By: Thorsten <thorsten@zed.dev>
2024-03-01 11:41:52 +01:00
Nathan Sobo
a165e997ba WIP: Compare occlusion ids instead of bounds in mouse listeners 2024-02-29 22:34:17 -07:00
Nathan Sobo
8444b11e76 Checkpoint 2024-02-29 20:31:12 -07:00
Nathan Sobo
609370f9d6 Merge branch 'fix-todo-comments' into fix-hover 2024-02-29 18:15:51 -07:00
Nathan Sobo
2108c764ad Remove ! from todo!() in comments
This practice makes it difficult to locate todo!s in my code when I'm working.
Let's take out the bang if we want to keep doing this.
2024-02-29 17:42:24 -07:00
Nathan Sobo
bdedeab7af Get GPUI compiling with some todos 2024-02-29 17:34:36 -07:00
Marshall Bowers
dab886f479 Stub out support for Azure OpenAI (#8624)
This PR stubs out support for [Azure
OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/overview)
within the `OpenAiCompletionProvider`.

It still requires some additional wiring so that it is accessible, but
the necessary hooks should be in place now.

Release Notes:

- N/A
2024-02-29 13:02:08 -05:00
Sai Gokula Krishnan
cbcd011a36 Improve matches on command palette (#8515)
Release Notes:

- Fixed consecutive spaces in command palette influencing selection.
#8184

Optionally, include screenshots / media showcasing your addition that
can be included in the release notes.



https://github.com/zed-industries/zed/assets/25414681/a4682247-f52c-4ab9-a32a-51ab5cf3dbcc
2024-02-29 18:31:13 +01:00
Antonio Scandurra
542fb5c89a WIP 2024-02-29 18:00:03 +01:00
Conrad Irwin
b3b94e64ba Ensure panel and pane sizes are integral (#8619)
Fixes: #8050

For some reason that we didn't investigate, if you have view caching
enabled,
and you have non-integer sized bounds, and you are right aligning
things, the
co-ordinates can differ by +/- 1px when using the cached view.

The easiest fix for now is to just not do that.

Co-Authored-By: Antonio <as-cii@zed.dev>

Release Notes:

- Fixed the pane icons flickering
([#8050](https://github.com/zed-industries/zed/issues/8050)).

Co-authored-by: Antonio <as-cii@zed.dev>
2024-02-29 09:16:42 -07:00
Rajesh Malviya
db9cc42245 Support Sourcehut & Codeberg in permalinks (#8616)
Updates #5110 

Release Notes:

- Added support for repositories hosted on `git.sr.ht` (Sourcehut) and
`codeberg.org` to the `editor: copy permalink to line` and `editor: open
permalink to line` actions
([#5110](https://github.com/zed-industries/zed/issues/5110)).

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-02-29 11:13:37 -05:00
Antonio Scandurra
0fde56909c WIP 2024-02-29 16:27:19 +01:00
Marshall Bowers
faa6f979be Restore signature of build_permalink (#8609)
This PR restores the original signature of `build_permalink`, which
intentionally uses a params struct to avoid mixing up the various `&str`
params that could otherwise be accidentally provided in the wrong order
without being caught by the compiler.

Release Notes:

- N/A
2024-02-29 08:52:09 -05:00
Thorsten Ball
dc7befb884 Bind ctrl-w to DeleteToPreviousWordStart (#8606)
Even though I use Vim mode, I'd love to have this in the command
palette/fuzzy finder. It's an Emacs keybinding, but also supported by
macOS nearly everywhere.

Release Notes:

- N/A
2024-02-29 14:11:36 +01:00
Thorsten Ball
519655297a Support Bitbucket.org in permalinks (#8601)
This adds support for Bitbucket.org/Bitbucket Cloud repositories to the
`editor: copy permalink to line` and `editor: open permalink to line`
actions.

Fixes #5110.



Release Notes:

- Added support for repositories hosted on Bitbucket.org (Bitbucket
Cloud) to the `editor: copy permalink to line` and `editor: open
permalink to line` actions.
([#5110](https://github.com/zed-industries/zed/issues/5110)).
2024-02-29 14:11:30 +01:00
Jason Lee
47bcb305af Use Pointer cursor style on Recent Projects, VCS Menu. (#8595)
Release Notes:

- Improved to use `Pointer` style cursor on VCS and Recent Projects
menu.



https://github.com/zed-industries/zed/assets/5518/4f638c6a-00b8-4fa8-b469-4d3109827bc2
2024-02-29 12:27:56 +01:00
Kirill Bulatov
953bc5eee2 Fix post-merge issue from the old branch PR (#8590)
Follow-up of https://github.com/zed-industries/zed/pull/6924/files that
had old code in CI that worked, but fresh `main` had different code that
needed small changes.


Release Notes:

- N/A
2024-02-29 11:46:00 +02:00
Antonio Scandurra
d75ef8e62d Simplify control flow in view caching 2024-02-29 10:17:11 +01:00
Yangze Luo
c94852b843 Go to reference when there's only one (#6924)
Fixes #4796

- Improved Go To Definition usability when there's a single reference ([4796](https://github.com/zed-industries/zed/issues/4796))

---------

Co-authored-by: Kirill Bulatov <kirill@zed.com>
2024-02-29 11:11:16 +02:00
Thorsten Ball
81886a9baf Fix default Vim keybinds for GoTo(Type)DefinitionSplit (#8587)
Follow-up to #8574

Release Notes:

- N/A
2024-02-29 10:06:17 +01:00
Jason Lee
225dd0f9a0 Improve extensions UI detail (#8578)
Release Notes:

- Improved "Extensions" UI details to tidy layout, add border, add
placeholder to search input.


## Before


![SCR-20240229-fbk](https://github.com/zed-industries/zed/assets/5518/a62608e2-8f79-48fe-9e1c-87d6eb59ddc9)

## After


![SCR-20240229-fhz](https://github.com/zed-industries/zed/assets/5518/d5c97711-ff93-4944-8e23-39b60dda88fc)

![SCR-20240229-fg9](https://github.com/zed-industries/zed/assets/5518/a24ad839-3b69-4ca7-813f-00375a81a008)

![SCR-20240229-fbb](https://github.com/zed-industries/zed/assets/5518/ff45d6c3-93a3-431c-81f5-edc9e7aa68d6)

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
2024-02-28 22:34:40 -05:00
Conrad Irwin
778b6fb27b Add OpenExcerptsSplit (#8574)
I would like to keep diagnostics open on one side, and process them on
the other.


Release Notes:

- Added `editor::OpenExcerptsSplit` (bound to `cmd-k enter`) to open the
selected excerpts in the adjacent pane
- vim: Added `ctrl-w d`, `ctrl-w shift-d` and `ctrl-w space` for
`editor::GoTo{,Type}Definition` and `editor::OpenExcerptsSplit`
2024-02-28 19:23:36 -07:00
Kirill Bulatov
b7429bf29d Added menu::UseSelectedQuery command that populates task modal query with the selected task name (#8572) 2024-02-29 02:20:43 +02:00
Conrad Irwin
9bd5ebb74b Revert "Introduce a new ToggleGraphicsProfiler command (#7607)" (#8567)
This reverts commit 0cebf68306.

Although this thing is very cool, it is a top source of crashes.

Example crash:
```
Segmentation fault: 11 on thread 26
  objc_retain +16
  invocation function for block in Overlay::onCommandBufferCommit(id<MTLCommandBuffer>) +60
  MTLDispatchListApply +52
```

Release Notes:

- Removed "Toggle Graphics Profiler" as it crashes too much.
2024-02-28 16:39:51 -07:00
Kirill Bulatov
ac30ded80e Allow .zed/tasks.json local configs (#8536)
![image](https://github.com/zed-industries/zed/assets/2690773/e1511777-b4ca-469e-8636-1e513b615368)

Follow-up of
https://github.com/zed-industries/zed/issues/7108#issuecomment-1960746397

Makes more clear where each task came from, auto (re)load
.zed/config.json changes, properly filtering out other worktree tasks.

Release Notes:

- Added local task configurations
2024-02-29 01:18:13 +02:00
Rom Grk
7f954cbbb8 linux: improve key translation (#8560)
This PR brings linux XKB key translation more in line with the macOS
logic, which fixes quite a few key bindings.
2024-02-28 15:13:02 -08:00
Rajas Paranjpe
c07237df33 Add other vkcube command to Linux docs (#8543)
Add other vkcube command to Linux docs

Release Notes: 

- N/A
2024-02-28 15:07:28 -08:00
hamza72x
387c161d8c Add libssl-dev for apt dependencies (#8512)
While building on Ubuntu (arm64). I had to manually install
`libssl-dev`.
Just added that in `script/linux`.
2024-02-28 15:06:11 -08:00
Roman
b76e0d997e Linux: Rewrite the event loop using calloop (#8314)
This PR unifies the event loop code for Wayland and X11. On Wayland,
blocking dispatch is now used. On X11, the invisible window is no longer
needed.

Release Notes:

- N/A

---------

Co-authored-by: Dzmitry Malyshau <kvark@fastmail.com>
Co-authored-by: Tadeo Kondrak <me@tadeo.ca>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
Co-authored-by: julia <julia@zed.dev>
2024-02-28 14:59:11 -08:00
Conrad Irwin
198dfe0097 Maybe make cherry-pick-bot better 2024-02-28 15:26:30 -07:00
Conrad Irwin
16eb17e2f8 Enable cherry-pick-bot (#8561)
You will now be able to say `/cherry-pick v0.123.x` to cherry-pick to a
different branch.

Release Notes:

- N/A
2024-02-28 15:19:16 -07:00
Conrad Irwin
014e6f66bb gpui: Don't impl IntoElement on () (#8555)
Although it's kinda cute, rust makes it too easy to accidentally return
() from a function.

/cc @nathansobo

Release Notes:

- N/A
2024-02-28 15:19:05 -07:00
Max Brunsfeld
9e4b3ce94c Avoid an unwrap when loading languages (#8562)
We couldn't reproduce the panic, but I believe it was possible when
uninstalling an extension while one if its grammars was still loading.

Release Notes:
- Fixed a crash that could happen when uninstalling a language extension
while its grammar was loading.

---------

Co-authored-by: Conrad <conrad@zed.dev>
2024-02-28 14:08:45 -08:00
Joseph T. Lyons
4a4ca2c3b8 Fix view release notes locally (#8553)
🤦‍♂️

Release Notes:

- N/A
2024-02-28 15:50:10 -05:00
Conrad Irwin
495de89747 Fix update notifications (#8554)
Fixes: #7597

Release Notes:

- Fixed update notifications
([#7597](https://github.com/zed-industries/zed/issues/7597)).
2024-02-28 13:43:54 -07:00
Conrad Irwin
6a3ac94eea Fix logic for view to send on Follow (#8549)
Before this we would erronously send no view in some cases.

Release Notes:

- N/A
2024-02-28 12:52:03 -07:00
Antonio Scandurra
f5cd8247d1 WIP 2024-02-28 19:59:49 +01:00
Antonio Scandurra
821960bf14 Rename commit_root to layout
Co-Authored-By: Nathan <nathan@zed.dev>
2024-02-28 19:27:12 +01:00
Joseph T. Lyons
6e04c1f924 v0.126.x dev 2024-02-28 13:13:51 -05:00
Antonio Scandurra
57f5f128f3 Fix flickering cursor style when a pane was zoomed (#8546)
Release Notes:

- N/A

Co-authored-by: Max Brunsfeld <max@zed.dev>
2024-02-28 18:50:48 +01:00
Thorsten Ball
7efa8d079d Do not set rules by default for eslint (#8545)
Follow-up to and fix for #8537.

Turns out that if you set `rules: []` it doesn't mean "no matchers", but
it means "no rules". So let's not set a default here.

Release Notes:

- N/A, see #8537

Co-authored-by: Conrad <conrad@zed.dev>
2024-02-28 18:39:54 +01:00
Andrew Lygin
d0ffd51bb1 Optimize file finder subscriptions (#8343)
Fixes #7519 

Optimizes file finder subscriptions &mdash; it now only subscribes to
worktrees updates instead of all project updates.

Project panel could also be optimized this way, I guess.

Release Notes:

- Fix selection resets in the file finder during language server
startup ([7519](https://github.com/zed-industries/zed/issues/7519))
2024-02-28 19:02:21 +02:00
Antonio Scandurra
7aba9eb4b7 Introduce a short-term solution for flickering (#8542)
This uses bounds checking alone to determine hover state to avoid
flicker. It's a short-term solution because the rendering is incorrect.
We think this is better than flickering though and buys us some time as
we work on a more robust solution overall.

Release Notes:

- Fixed flickering when hovering.

---------

Co-authored-by: Nathan <nathan@zed.dev>
2024-02-28 17:57:20 +01:00
Hugo Urías
517ea734ee Add Coffeescript, Scala, FSharp, TCL and Nim icons and add SQL as "storage" (#8447)
I would like to add these file icons all from the source svgrepo.com and
with a size of 14x14. Also I've modified file_types.json in order to add
the file types and path to the image aswell as added SQL as a storage
type so it's linked to an icon.

Here is how these new changes would look like:
<img width="240" alt="Captura de pantalla 2024-02-26 a las 19 30 33"
src="https://github.com/zed-industries/zed/assets/93369643/73e50e4a-bfe8-4239-b919-280150051e36">

Release Notes:

- Added icons for Coffeescript, F#, Nim, Scala, and TCL files.
- Updated icon for SQL files.
2024-02-28 11:09:29 -05:00
Thorsten Ball
a52177fd39 Allow users to configure ESLint codeActionOnSave settings (#8537)
This fixes #8533 by allowing users to specify the settings that are
passed to ESLint on workspace initialization.

Example Zed `settings.json` to enable `fixAll` for eslint when
saving/formatting, but only for the `import/order` rule:

```json
{
  "languages": {
    "JavaScript": {
      "code_actions_on_format": {
        "source.fixAll.eslint": true
      }
    }
  },
  "lsp": {
    "eslint": {
      "settings": {
        "codeActionOnSave": {
          "rules": ["import/order"]
        }
      }
    },
  }
}
```

The possible settings are described in the README of `vscode-eslint`
here:
https://github.com/Microsoft/vscode-eslint?tab=readme-ov-file#settings-options

- `eslint.codeActionsOnSave.enable` (default: `true`, config key in Zed:
`lsp.eslint.settings.codeActionOnSave.enable`)
- `eslint.codeActionsOnSave.mode` (default: not set by Zed, config key
in Zed: `lsp.eslint.settings.codeActionOnSave.mode`)
- `eslint.codeActionsOnSave.rules` (default: `[]`, config key in Zed:
`lsp.eslint.settings.codeActionOnSave.rules`)

Yes, in the readme it's plural: `codeActionsOnSave`, but since
`eslint-vscode` we're using this old release:


https://github.com/microsoft/vscode-eslint/releases/tag/release%2F2.2.20-Insider

We use the singular version:
https://github.com/microsoft/vscode-eslint/blob/release/2.2.20-Insider/server/src/eslintServer.ts#L461

Our schema looks like this:

```json
{
  "lsp": {
    "eslint": {
      "settings": {
        "codeActionOnSave": {
          "enable": true,
          "rules": ["import/order"],
          "mode": "all"
        }
      }
    },
  }
}
```

We should probably fix this and upgrade to the newest version of ESLint.

Release Notes:

- Added ability for users to configure settings for ESLint's
`codeActionOnSave`, e.g. specifying `rules` that should be respected
when also using `"code_actions_on_format": {"source.fixAll.eslint":
true}`. These settings can be passed to ESLint as part of the `"lsp"`
part of the Zed settings. Example: `{"lsp": {"eslint": {"settings":
{"codeActionOnSave": { "rules": ["import/order"] }}}}}`
([#8533](https://github.com/zed-industries/zed/issues/8533)).

Demo:


https://github.com/zed-industries/zed/assets/1185253/5c0cf900-9acb-4a70-b89d-49b6eeb6f0e4
2024-02-28 17:04:36 +01:00
Dedar Alam
893e55ff96 Downscale source file icons to 14x14 (#8521)
Update file icon size to 14 * 14
- css 
- bun 
- erlang 
- terraform 
- php 
- haskell

Release Notes:

- N/A
2024-02-28 11:04:27 -05:00
Antonio Scandurra
0ab1e6f451 WIP 2024-02-28 14:29:59 +01:00
Thorsten Ball
f8959834c4 Add ability to run ESLint (and other non-primary language server) code actions on format (#8496)
This PR does two things to fix
https://github.com/zed-industries/zed/issues/4325:

1. It changes the way `code_actions_on_format` works to send the
possibly configured code actions to _all_ (and not just the primary)
languages servers. That means configured code actions can now be sent to
ESLint, tailwind, ... and other language servers.
2. It enables `codeActionsOnSave` by default for ESLint. That does
**not** mean that by default we will run something on save, but only
that we enable it for ESLint.

Users can then configure their Zed to run the `eslint` code action on
format. Example, for JavaScript:

```json
    {
      "languages": {
        "JavaScript": {
          "code_actions_on_format": {
            "source.fixAll.eslint": true
          }
        },
      }
    }
```

Release Notes:

- Added ability to run ESLint fixes when formatting a buffer. Code
actions configured in
[`code_actions_on_format`](https://zed.dev/docs/configuring-zed#code-actions-on-format)
are now being sent to _all_ language servers connected to a buffer, not
just the primary one. So if a user now sets `"code_actions_on_format": {
"source.fixAll.eslint": true }` in their Zed settings, the
`source.fixAll.eslint` code action will be sent to ESLint, which is not
a primary language server. Since the formatter (prettier, or external
commands, or another language server, ...) still runs, it's important
that these code actions and the formatter don't clash.
([#4325](https://github.com/zed-industries/zed/issues/4325)).

Demo:



https://github.com/zed-industries/zed/assets/1185253/9ef03ad5-1f5c-4d46-b72a-eef611e32f39
2024-02-28 13:55:20 +01:00
Kirill Bulatov
2e516261fe Add tests on inventory task sorting 2024-02-28 14:13:40 +02:00
Kirill Bulatov
ca092fb694 Move NumericPrefixWithSuffix into utils 2024-02-28 14:13:40 +02:00
Kirill Bulatov
96d9df073e Sort tasks modal entries by last used time 2024-02-28 14:13:40 +02:00
Thorsten Ball
9f7e625d37 Fix formatting of code in prettier crate (#8526)
Came across this code, saw lots of blue squiggly lines, saw a chance to
simplify the code a little bit and reduce indentation.

(Kinda ironic that I'm the one formatting the prettier code, right?)

Release Notes:

- N/A
2024-02-28 12:51:08 +01:00
Nathan Sobo
17d736b23d WIP: Move constructor of cursor names to after_layout 2024-02-27 21:18:09 -07:00
Nathan Sobo
6006b171f7 WIP 2024-02-27 20:26:12 -07:00
Nathan Sobo
5790d9ba27 WIP: Rename layout phase methods and give each a state
Shred mode. I don't want to force things to be optional just to account for both phases though.
2024-02-27 20:08:35 -07:00
Nathan Sobo
d5766dc69f Rename element methods 2024-02-27 13:30:40 -07:00
Nathan Sobo
3e5c11dc41 Eliminate with_z_index 2024-02-27 13:19:29 -07:00
Antonio Scandurra
ed1a256f99 Add a new commit_root method 2024-02-27 19:42:15 +01:00
Antonio Scandurra
28e795f2fd WIP: start removing draw as it won't work with the two passes 2024-02-27 18:14:56 +01:00
Antonio Scandurra
98374a70d3 Introduce a new Element::commit_bounds method 2024-02-27 16:09:32 +01:00
Antonio Scandurra
33abbcb535 Simplify DrawableElement and expose AnyElement::downcast_mut 2024-02-27 14:01:44 +01:00
Antonio Scandurra
7481c0d556 Remove Element::element_id method from trait 2024-02-27 13:52:57 +01:00
Antonio Scandurra
a1d5249b8e Fix regression in Div and simplify painting 2024-02-27 13:39:07 +01:00
Antonio Scandurra
21bde9b653 💄 2024-02-27 11:07:39 +01:00
Antonio Scandurra
5183dbb5be Fix remaining compile errors and runtime panics 2024-02-27 10:33:20 +01:00
Nathan Sobo
7c6e6971da WIP: Separate ElementState from FrameState 2024-02-26 23:16:23 -07:00
Nathan Sobo
81b5c74b0e Rename Element::State -> FrameState 2024-02-26 20:42:03 -07:00
Nathan Sobo
47e48e20d3 Checkpoint 2024-02-26 20:27:57 -07:00
Nathan Sobo
4584c5e2b3 WIP 2024-02-26 17:46:56 -07:00
Nathan Sobo
e07c53c27f Checkpoint 2024-02-26 15:44:07 -07:00
Nathan Sobo
e917d1d251 Cherry-pick BoundsTree from a previous attempt 2024-02-26 15:35:09 -07:00
178 changed files with 8884 additions and 6988 deletions

2
.github/cherry-pick-bot.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
enabled: true
preservePullRequestTitle: true

View File

@@ -99,7 +99,7 @@ jobs:
- name: Build other binaries and features
run: cargo build --workspace --bins --all-features; cargo check -p gpui --features "macos-blade"
# todo!(linux): Actually run the tests
# todo(linux): Actually run the tests
linux_tests:
name: (Linux) Run Clippy and tests
runs-on: ubuntu-latest
@@ -126,7 +126,7 @@ jobs:
- name: Build Zed
run: cargo build -p zed
# todo!(windows): Actually run the tests
# todo(windows): Actually run the tests
windows_tests:
name: (Windows) Run Clippy and tests
runs-on: windows-latest

149
Cargo.lock generated
View File

@@ -404,9 +404,9 @@ dependencies = [
[[package]]
name = "async-compression"
version = "0.4.6"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c"
checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5"
dependencies = [
"flate2",
"futures-core",
@@ -681,9 +681,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
[[package]]
name = "async-trait"
version = "0.1.77"
version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
@@ -1631,6 +1631,32 @@ dependencies = [
"util",
]
[[package]]
name = "calloop"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298"
dependencies = [
"bitflags 2.4.1",
"log",
"polling 3.3.2",
"rustix 0.38.30",
"slab",
"thiserror",
]
[[package]]
name = "calloop-wayland-source"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
dependencies = [
"calloop",
"rustix 0.38.30",
"wayland-backend",
"wayland-client",
]
[[package]]
name = "castaway"
version = "0.1.2"
@@ -2421,18 +2447,18 @@ dependencies = [
[[package]]
name = "cranelift-bforest"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29a6391a9172a93f413370fa561c6bca786e06c89cf85f23f02f6345b1c8ee34"
checksum = "9515fcc42b6cb5137f76b84c1a6f819782d0cf12473d145d3bc5cd67eedc8bc2"
dependencies = [
"cranelift-entity",
]
[[package]]
name = "cranelift-codegen"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "409c6cbb326604a53ec47eb6341fc85128f24c81012a014b4c728ed24f6e9350"
checksum = "1ad827c6071bfe6d22de1bc331296a29f9ddc506ff926d8415b435ec6a6efce0"
dependencies = [
"bumpalo",
"cranelift-bforest",
@@ -2451,33 +2477,33 @@ dependencies = [
[[package]]
name = "cranelift-codegen-meta"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fff55e100130995b9ad9ac6b03a24ed5da3c1a1261dcdeb8a7a0292656994fb3"
checksum = "10e6b36237a9ca2ce2fb4cc7741d418a080afa1327402138412ef85d5367bef1"
dependencies = [
"cranelift-codegen-shared",
]
[[package]]
name = "cranelift-codegen-shared"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1446e2eb395fc7b3019a36dccb7eccea923f6caf581b903c8e7e751b6d214a7"
checksum = "c36bf4bfb86898a94ccfa773a1f86e8a5346b1983ff72059bdd2db4600325251"
[[package]]
name = "cranelift-control"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24076ecf69cbf8b9e1e532ae8e7ac01d850a1c2e127058a26eb3245f9d5b89d1"
checksum = "7cbf36560e7a6bd1409ca91e7b43b2cc7ed8429f343d7605eadf9046e8fac0d0"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f40df95180ad317c60459bb90dd87803d35e538f4c54376d8b26c851f6f0a1b"
checksum = "a71e11061a75b1184c09bea97c026a88f08b59ade96a7bb1f259d4ea0df2e942"
dependencies = [
"serde",
"serde_derive",
@@ -2485,9 +2511,9 @@ dependencies = [
[[package]]
name = "cranelift-frontend"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c3974cc665b699b626742775dae1c1cdea5170f5028ab1f3eb61a7a9a6e2979"
checksum = "af5d4da63143ee3485c7bcedde0a818727d737d1083484a0ceedb8950c89e495"
dependencies = [
"cranelift-codegen",
"log",
@@ -2497,15 +2523,15 @@ dependencies = [
[[package]]
name = "cranelift-isle"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99543f92b9c361f3c54a29e945adb5b9ef1318feaa5944453cabbfcb3c495919"
checksum = "457a9832b089e26f5eea70dcf49bed8ec6edafed630ce7c83161f24d46ab8085"
[[package]]
name = "cranelift-native"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c0d84dc7d9b3f73ad565eacc4ab36525c407ef5150893b4b94d5f5f904eb48a"
checksum = "9b490d579df1ce365e1ea359e24ed86d82289fa785153327c2f6a69a59a731e4"
dependencies = [
"cranelift-codegen",
"libc",
@@ -2514,9 +2540,9 @@ dependencies = [
[[package]]
name = "cranelift-wasm"
version = "0.105.1"
version = "0.105.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53781039219944d59c6d3ec57e6cae31a1a33db71573a945d84ba6d875d0a743"
checksum = "8cd747ed7f9a461dda9c388415392f6bb95d1a6ef3b7694d17e0817eb74b7798"
dependencies = [
"cranelift-codegen",
"cranelift-entity",
@@ -4002,6 +4028,8 @@ dependencies = [
"blade-macros",
"block",
"bytemuck",
"calloop",
"calloop-wayland-source",
"cbindgen",
"cocoa",
"collections",
@@ -7571,9 +7599,9 @@ dependencies = [
[[package]]
name = "rust-embed"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -7582,9 +7610,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8"
dependencies = [
"proc-macro2",
"quote",
@@ -7595,9 +7623,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581"
dependencies = [
"globset",
"sha2 0.10.7",
@@ -8318,9 +8346,9 @@ dependencies = [
[[package]]
name = "shlex"
version = "1.3.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]]
name = "signal-hook"
@@ -9101,7 +9129,6 @@ dependencies = [
"collections",
"futures 0.3.28",
"gpui",
"project_core",
"schemars",
"serde",
"serde_json_lenient",
@@ -9117,12 +9144,11 @@ dependencies = [
"fuzzy",
"gpui",
"language",
"lsp",
"menu",
"picker",
"project",
"project_core",
"serde",
"serde_json",
"task",
"ui",
"util",
@@ -9896,7 +9922,7 @@ dependencies = [
[[package]]
name = "tree-sitter-gitcommit"
version = "0.3.3"
source = "git+https://github.com/gbprod/tree-sitter-gitcommit#7c01af8d227b5344f62aade2ff00f19bd0c458ca"
source = "git+https://github.com/gbprod/tree-sitter-gitcommit#e8d9eda4e5ea0b08aa39d48dab0f6553058fbe0f"
dependencies = [
"cc",
"tree-sitter",
@@ -10492,6 +10518,7 @@ dependencies = [
"take-until",
"tempfile",
"tendril",
"unicase",
"url",
]
@@ -10771,9 +10798,9 @@ dependencies = [
[[package]]
name = "wasmtime"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06f80b13fdeba0ea5267813d0f06af822309f7125fc8db6094bcd485f0a4ae7"
checksum = "4c843b8bc4dd4f3a76173ba93405c71111d570af0d90ea5f6299c705d0c2add2"
dependencies = [
"anyhow",
"bincode",
@@ -10801,18 +10828,18 @@ dependencies = [
[[package]]
name = "wasmtime-asm-macros"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d7395b475c6f858c7edfce375f00d8282a32fbf5d1ebc93eddfac5c2458a52"
checksum = "86b9d329c718b3a18412a6a017c912b539baa8fe1210d21b651f6b4dbafed743"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "wasmtime-c-api-impl"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c09ac0c18464f8ef0b554c12defc94e3fc082b62309a3da229de60d47cf75a"
checksum = "cc93587c24d8e3cb28912eb7abf95f7e350380656faccc46cff04c0821ec58c2"
dependencies = [
"anyhow",
"log",
@@ -10824,9 +10851,9 @@ dependencies = [
[[package]]
name = "wasmtime-c-api-macros"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "864c4a337294fe690f02b39f2b3f45414447d9321d0ed24d3dc7696bf291e789"
checksum = "2e571a71eba52dfe81ef653a3a336888141f00fc2208a9962722e036fe2a34be"
dependencies = [
"proc-macro2",
"quote",
@@ -10834,9 +10861,9 @@ dependencies = [
[[package]]
name = "wasmtime-cranelift"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "974d9455611e26c97d31705e19545de58fa8867416592bd93b7a54a7fc37cedb"
checksum = "31ca62f519225492bd555d0ec85a2dacb0c10315db3418c8b9aeb3824bf54a24"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
@@ -10859,9 +10886,9 @@ dependencies = [
[[package]]
name = "wasmtime-cranelift-shared"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40667ba458634db703aea3bd960e80bc9352c21d5e765b69f43e3b0c964eb611"
checksum = "fd5f2071f42e61490bf7cb95b9acdbe6a29dd577a398019304a960585f28b844"
dependencies = [
"anyhow",
"cranelift-codegen",
@@ -10875,9 +10902,9 @@ dependencies = [
[[package]]
name = "wasmtime-environ"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8da991421528c2767053cb0cfa70b5d28279100dbcf70ed7f74b51abe1656ef"
checksum = "82bf1a47f384610da19f58b0fd392ca6a3b720974315c08afb0392c0f3951fed"
dependencies = [
"anyhow",
"bincode",
@@ -10896,9 +10923,9 @@ dependencies = [
[[package]]
name = "wasmtime-jit-icache-coherence"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3346431a41fbb0c5af0081c2322361b00289f2902e54ee7b115e9b2ad32b156b"
checksum = "33f4121cb29dda08139b2824a734dd095d83ce843f2d613a84eb580b9cfc17ac"
dependencies = [
"cfg-if 1.0.0",
"libc",
@@ -10907,9 +10934,9 @@ dependencies = [
[[package]]
name = "wasmtime-runtime"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a489353aa297b46a66cde8da48cab8e1e967e7f4b0ae3d9889a0550bf274810b"
checksum = "4e517f2b996bb3b0e34a82a2bce194f850d9bcfc25c08328ef5fb71b071066b8"
dependencies = [
"anyhow",
"cc",
@@ -10934,9 +10961,9 @@ dependencies = [
[[package]]
name = "wasmtime-types"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12c56e31fd7fa707fbd7720b2b29ac42ccfb092fe9d85c98f1d3988f9a1d4558"
checksum = "54a327d7a0ef57bd52a507d28b4561a74126c7a8535a2fc6f2025716bc6a52e8"
dependencies = [
"cranelift-entity",
"serde",
@@ -10947,9 +10974,9 @@ dependencies = [
[[package]]
name = "wasmtime-versioned-export-macros"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b0300976c36a9427d184e3ecf7c121c2cb3f030844faf9fcb767821e9d4c382"
checksum = "8ef32eea9fc7035a55159a679d1e89b43ece5ae45d24eed4808e6a92c99a0da4"
dependencies = [
"proc-macro2",
"quote",
@@ -10958,9 +10985,9 @@ dependencies = [
[[package]]
name = "wasmtime-wmemcheck"
version = "18.0.1"
version = "18.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acdf5b8da6ebf7549dad0cd32ca4a3a0461449ef4feec9d0d8450d8da9f51f9b"
checksum = "7f4cbfb052d66f03603a9b77f18171ea245c7805714caad370a549a6344bf86b"
[[package]]
name = "wayland-backend"
@@ -11607,7 +11634,7 @@ dependencies = [
[[package]]
name = "zed"
version = "0.125.0"
version = "0.126.0"
dependencies = [
"activity_indicator",
"anyhow",

View File

@@ -294,6 +294,7 @@ tree-sitter-vue = { git = "https://github.com/zed-industries/tree-sitter-vue", r
tree-sitter-yaml = { git = "https://github.com/zed-industries/tree-sitter-yaml", rev = "f545a41f57502e1b5ddf2a6668896c1b0620f930" }
tree-sitter-zig = { git = "https://github.com/maxxnino/tree-sitter-zig", rev = "0d08703e4c3f426ec61695d7617415fff97029bd" }
unindent = "0.1.7"
unicase = "2.6"
url = "2.2"
uuid = { version = "1.1.2", features = ["v4"] }
wasmtime = "18.0"
@@ -309,7 +310,7 @@ pathfinder_simd = { git = "https://github.com/servo/pathfinder.git", rev = "e4fc
split-debuginfo = "unpacked"
debug = "limited"
# todo!(linux) - Remove this
# todo(linux) - Remove this
[profile.dev.package.blade-graphics]
split-debuginfo = "off"
debug = "full"

View File

@@ -1,3 +1,6 @@
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22.596c6.628 0 12-4.338 12-9.688 0-3.318-2.057-6.248-5.219-7.986-1.286-.715-2.297-1.357-3.139-1.89C14.058 2.025 13.08 1.404 12 1.404c-1.097 0-2.334.785-3.966 1.821a49.92 49.92 0 0 1-2.816 1.697C2.057 6.66 0 9.59 0 12.908c0 5.35 5.372 9.687 12 9.687v.001ZM10.599 4.715c.334-.759.503-1.58.498-2.409 0-.145.202-.187.23-.029.658 2.783-.902 4.162-2.057 4.624-.124.048-.199-.121-.103-.209a5.763 5.763 0 0 0 1.432-1.977Zm2.058-.102a5.82 5.82 0 0 0-.782-2.306v-.016c-.069-.123.086-.263.185-.172 1.962 2.111 1.307 4.067.556 5.051-.082.103-.23-.003-.189-.126a5.85 5.85 0 0 0 .23-2.431Zm1.776-.561a5.727 5.727 0 0 0-1.612-1.806v-.014c-.112-.085-.024-.274.114-.218 2.595 1.087 2.774 3.18 2.459 4.407a.116.116 0 0 1-.049.071.11.11 0 0 1-.153-.026.122.122 0 0 1-.022-.083 5.891 5.891 0 0 0-.737-2.331Zm-5.087.561c-.617.546-1.282.76-2.063 1-.117 0-.195-.078-.156-.181 1.752-.909 2.376-1.649 2.999-2.778 0 0 .155-.118.188.085 0 .304-.349 1.329-.968 1.874Zm4.945 11.237a2.957 2.957 0 0 1-.937 1.553c-.346.346-.8.565-1.286.62a2.178 2.178 0 0 1-1.327-.62 2.955 2.955 0 0 1-.925-1.553.244.244 0 0 1 .064-.198.234.234 0 0 1 .193-.069h3.965a.226.226 0 0 1 .19.07c.05.053.073.125.063.197Zm-5.458-2.176a1.862 1.862 0 0 1-2.384-.245 1.98 1.98 0 0 1-.233-2.447c.207-.319.503-.566.848-.713a1.84 1.84 0 0 1 1.092-.11c.366.075.703.261.967.531a1.98 1.98 0 0 1 .408 2.114 1.931 1.931 0 0 1-.698.869v.001Zm8.495.005a1.86 1.86 0 0 1-2.381-.253 1.964 1.964 0 0 1-.547-1.366c0-.384.11-.76.32-1.079.207-.319.503-.567.849-.713a1.844 1.844 0 0 1 1.093-.108c.367.076.704.262.968.534a1.98 1.98 0 0 1 .4 2.117 1.932 1.932 0 0 1-.702.868Z"/>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7 13.179688 C 10.867188 13.179688 14 10.652344 14 7.53125 C 14 5.59375 12.800781 3.886719 10.957031 2.871094 C 10.207031 2.453125 9.617188 2.078125 9.125 1.769531 C 8.199219 1.179688 7.628906 0.820312 7 0.820312 C 6.359375 0.820312 5.636719 1.277344 4.6875 1.882812 C 4.148438 2.230469 3.601562 2.558594 3.042969 2.871094 C 1.199219 3.886719 0 5.59375 0 7.53125 C 0 10.652344 3.132812 13.179688 7 13.179688 Z M 6.183594 2.75 C 6.378906 2.308594 6.476562 1.828125 6.472656 1.34375 C 6.472656 1.261719 6.589844 1.234375 6.605469 1.328125 C 6.992188 2.953125 6.082031 3.757812 5.40625 4.027344 C 5.335938 4.054688 5.292969 3.953125 5.347656 3.902344 C 5.703125 3.582031 5.988281 3.191406 6.183594 2.75 Z M 7.382812 2.691406 C 7.328125 2.214844 7.171875 1.757812 6.925781 1.347656 L 6.925781 1.335938 C 6.886719 1.265625 6.976562 1.183594 7.035156 1.234375 C 8.179688 2.46875 7.796875 3.609375 7.359375 4.183594 C 7.3125 4.242188 7.226562 4.179688 7.25 4.109375 C 7.394531 3.652344 7.4375 3.167969 7.382812 2.691406 Z M 8.417969 2.363281 C 8.183594 1.949219 7.863281 1.589844 7.480469 1.308594 L 7.480469 1.300781 C 7.414062 1.253906 7.464844 1.140625 7.546875 1.175781 C 9.058594 1.808594 9.164062 3.03125 8.980469 3.746094 C 8.976562 3.761719 8.964844 3.777344 8.953125 3.785156 C 8.921875 3.808594 8.882812 3.800781 8.863281 3.773438 C 8.851562 3.757812 8.847656 3.742188 8.847656 3.722656 C 8.800781 3.246094 8.65625 2.78125 8.417969 2.363281 Z M 5.453125 2.691406 C 5.09375 3.007812 4.703125 3.132812 4.25 3.273438 C 4.179688 3.273438 4.132812 3.230469 4.15625 3.167969 C 5.179688 2.636719 5.542969 2.207031 5.90625 1.546875 C 5.90625 1.546875 5.996094 1.480469 6.015625 1.597656 C 6.015625 1.773438 5.8125 2.371094 5.453125 2.691406 Z M 8.335938 9.246094 C 8.253906 9.597656 8.0625 9.914062 7.789062 10.152344 C 7.589844 10.355469 7.324219 10.480469 7.039062 10.511719 C 6.746094 10.484375 6.472656 10.359375 6.265625 10.152344 C 5.996094 9.914062 5.808594 9.597656 5.726562 9.246094 C 5.71875 9.203125 5.734375 9.160156 5.761719 9.128906 C 5.792969 9.101562 5.835938 9.085938 5.875 9.089844 L 8.1875 9.089844 C 8.230469 9.085938 8.269531 9.101562 8.300781 9.132812 C 8.328125 9.160156 8.34375 9.203125 8.335938 9.246094 Z M 5.152344 7.976562 C 4.714844 8.273438 4.128906 8.210938 3.761719 7.832031 C 3.390625 7.445312 3.335938 6.855469 3.625 6.40625 C 3.746094 6.21875 3.917969 6.074219 4.121094 5.992188 C 4.320312 5.90625 4.542969 5.882812 4.757812 5.925781 C 4.972656 5.96875 5.167969 6.078125 5.320312 6.234375 C 5.636719 6.5625 5.730469 7.046875 5.558594 7.46875 C 5.476562 7.675781 5.335938 7.851562 5.152344 7.976562 Z M 10.109375 7.980469 C 9.671875 8.273438 9.085938 8.210938 8.71875 7.832031 C 8.511719 7.617188 8.398438 7.332031 8.398438 7.035156 C 8.398438 6.8125 8.464844 6.589844 8.585938 6.40625 C 8.707031 6.21875 8.878906 6.074219 9.082031 5.988281 C 9.28125 5.90625 9.503906 5.882812 9.71875 5.925781 C 9.933594 5.972656 10.128906 6.078125 10.285156 6.238281 C 10.597656 6.566406 10.691406 7.050781 10.515625 7.472656 C 10.433594 7.679688 10.292969 7.855469 10.109375 7.980469 Z M 10.109375 7.980469 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -1,4 +1,6 @@
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M24.235 6.519l-16.47-0.004 0.266 3.277 12.653 0.002-0.319 3.394h-8.298l0.3 3.215h7.725l-0.457 4.403-3.636 1.005-3.694-1.012-0.235-2.637h-3.262l0.362 4.817 6.829 2.128 6.714-1.912 1.521-16.675zM2.879 1.004h26.242l-2.387 26.946-10.763 3.045-10.703-3.047z"></path>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 10.601562 2.851562 L 3.398438 2.851562 L 3.511719 4.285156 L 9.050781 4.285156 L 8.910156 5.769531 L 5.28125 5.769531 L 5.410156 7.175781 L 8.789062 7.175781 L 8.589844 9.101562 L 7 9.542969 L 5.382812 9.097656 L 5.28125 7.945312 L 3.851562 7.945312 L 4.011719 10.054688 L 7 10.984375 L 9.9375 10.148438 Z M 1.257812 0.4375 L 12.742188 0.4375 L 11.695312 12.226562 L 6.988281 13.558594 L 2.304688 12.226562 Z M 1.257812 0.4375 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 730 B

View File

@@ -1 +1,12 @@
<svg height="64" viewBox="0 0 128 128" width="64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="0" x2="128" y1="128" y2="0"><stop offset="0" stop-color="#333"/><stop offset="1" stop-color="#5d5d5d"/></linearGradient><path d="m12.239265 30.664279h14.960911c-5.59432 5.460938-7.654216 10.692785-10.342106 18.023379-3.200764 8.729348-.549141 29.987457 3.815534 37.55289 2.943384 5.101853 6.282685 8.994876 8.233522 11.095173h-16.667861zm89.614855 0h13.90661v66.671442h-13.55518c1.31391-1.750328 3.43934-4.534454 5.12085-6.426163 2.32782-2.618784 4.97023-6.978412 4.97023-6.978412l-16.015202-8.133112s-5.48977 11.600331-15.964999 15.964998c-10.475214 4.364666-19.784679-.838179-25.604243-7.530659-5.819578-6.692502-5.82371-22.14014-5.82371-22.14014h60.797524c1.16391-14.839892-2.63216-21.249816-4.66901-25.90547-.91799-2.098266-1.89261-3.810819-3.16287-5.522484zm-38.356164 1.757154c.35429-.01632.731685-.0092 1.104497 0 11.930114.290977 13.053143 12.802122 13.053143 12.802122h-27.311192s2.170772-12.298638 13.153552-12.802122z" fill="url(#a)"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<defs>
<linearGradient id="linear0" gradientUnits="userSpaceOnUse" x1="0" y1="128" x2="128" y2="0" gradientTransform="matrix(0.109375,0,0,0.109375,0,0)">
<stop offset="0" style="stop-color:rgb(20%,20%,20%);stop-opacity:1;"/>
<stop offset="1" style="stop-color:rgb(36.470588%,36.470588%,36.470588%);stop-opacity:1;"/>
</linearGradient>
</defs>
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:url(#linear0);" d="M 1.339844 3.355469 L 2.976562 3.355469 C 2.363281 3.953125 2.136719 4.523438 1.84375 5.324219 C 1.492188 6.28125 1.785156 8.605469 2.261719 9.433594 C 2.582031 9.992188 2.949219 10.417969 3.160156 10.644531 L 1.339844 10.644531 Z M 11.140625 3.355469 L 12.660156 3.355469 L 12.660156 10.644531 L 11.179688 10.644531 C 11.324219 10.453125 11.554688 10.148438 11.738281 9.941406 C 11.992188 9.65625 12.28125 9.179688 12.28125 9.179688 L 10.53125 8.289062 C 10.53125 8.289062 9.929688 9.558594 8.785156 10.035156 C 7.640625 10.515625 6.621094 9.945312 5.984375 9.214844 C 5.347656 8.480469 5.347656 6.792969 5.347656 6.792969 L 11.996094 6.792969 C 12.125 5.167969 11.710938 4.46875 11.484375 3.957031 C 11.386719 3.726562 11.277344 3.542969 11.140625 3.355469 Z M 6.945312 3.546875 C 6.984375 3.542969 7.023438 3.546875 7.066406 3.546875 C 8.371094 3.578125 8.492188 4.945312 8.492188 4.945312 L 5.507812 4.945312 C 5.507812 4.945312 5.742188 3.601562 6.945312 3.546875 Z M 6.945312 3.546875 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -27,6 +27,7 @@
"css": "css",
"csv": "storage",
"cts": "typescript",
"coffee": "coffeescript",
"dart": "dart",
"dat": "storage",
"db": "storage",
@@ -48,6 +49,7 @@
"fmp": "storage",
"fp7": "storage",
"frm": "storage",
"fs": "fsharp",
"gdb": "storage",
"gif": "image",
"gitattributes": "vcs",
@@ -104,6 +106,7 @@
"myd": "storage",
"myi": "storage",
"nu": "terminal",
"nim": "nim",
"odp": "document",
"ods": "document",
"odt": "document",
@@ -138,6 +141,9 @@
"sqlite": "storage",
"svelte": "template",
"svg": "image",
"sc": "scala",
"scala": "scala",
"sql": "storage",
"swift": "swift",
"tf": "terraform",
"tfvars": "terraform",
@@ -148,6 +154,7 @@
"ttf": "font",
"tsx": "code",
"txt": "document",
"tcl": "tcl",
"vue": "vue",
"wav": "audio",
"webm": "video",
@@ -189,6 +196,9 @@
"css": {
"icon": "icons/file_icons/css.svg"
},
"coffeescript": {
"icon": "icons/file_icons/coffeescript.svg"
},
"dart": {
"icon": "icons/file_icons/dart.svg"
},
@@ -222,6 +232,9 @@
"font": {
"icon": "icons/file_icons/font.svg"
},
"fsharp": {
"icon": "icons/file_icons/fsharp.svg"
},
"haskell": {
"icon": "icons/file_icons/haskell.svg"
},
@@ -258,6 +271,9 @@
"ocaml": {
"icon": "icons/file_icons/ocaml.svg"
},
"nim": {
"icon": "icons/file_icons/nim.svg"
},
"phoenix": {
"icon": "icons/file_icons/phoenix.svg"
},
@@ -288,6 +304,9 @@
"storage": {
"icon": "icons/file_icons/database.svg"
},
"scala": {
"icon": "icons/file_icons/scala.svg"
},
"swift": {
"icon": "icons/file_icons/swift.svg"
},
@@ -306,6 +325,9 @@
"typescript": {
"icon": "icons/file_icons/typescript.svg"
},
"tcl": {
"icon": "icons/file_icons/tcl.svg"
},
"vcs": {
"icon": "icons/file_icons/git.svg"
},

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 3.324219 3.496094 C 1.507812 5.40625 0.0273438 6.984375 0.0273438 6.996094 C 0.0273438 7.015625 1.511719 8.59375 3.332031 10.503906 L 6.632812 13.980469 L 6.632812 10.472656 L 4.984375 8.734375 L 3.332031 6.996094 L 4.984375 5.257812 L 6.632812 3.519531 L 6.628906 1.773438 L 6.621094 0.0273438 Z M 3.324219 3.496094 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.125 1.773438 L 7.125 3.492188 L 8.769531 5.222656 C 9.675781 6.171875 10.421875 6.96875 10.425781 6.984375 C 10.433594 7.003906 9.691406 7.800781 8.78125 8.761719 L 7.125 10.503906 L 7.125 13.972656 L 7.214844 13.890625 C 7.296875 13.820312 8.203125 12.90625 9.167969 11.910156 C 9.398438 11.671875 9.605469 11.464844 9.621094 11.449219 C 9.671875 11.402344 11.261719 9.789062 11.601562 9.4375 C 11.773438 9.261719 11.957031 9.082031 12 9.035156 C 12.046875 8.988281 12.433594 8.59375 12.863281 8.160156 C 13.289062 7.726562 13.722656 7.289062 13.824219 7.183594 L 14.007812 6.996094 L 13.808594 6.796875 C 13.179688 6.167969 12.527344 5.503906 11.820312 4.785156 C 11.574219 4.53125 11.105469 4.058594 10.785156 3.734375 C 10.460938 3.414062 9.871094 2.8125 9.472656 2.402344 C 9.074219 1.996094 8.609375 1.515625 8.4375 1.339844 C 8.265625 1.160156 7.910156 0.800781 7.652344 0.539062 C 7.394531 0.273438 7.167969 0.0585938 7.15625 0.0585938 C 7.132812 0.0585938 7.125 0.59375 7.125 1.773438 Z M 7.125 1.773438 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.453125 5.757812 C 4.308594 6.964844 4.285156 6.992188 4.332031 7.050781 C 4.359375 7.082031 4.886719 7.636719 5.5 8.289062 L 6.621094 9.464844 L 6.628906 8.222656 C 6.632812 7.539062 6.632812 6.429688 6.628906 5.753906 L 6.621094 4.527344 Z M 5.453125 5.757812 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1,13 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 32 32"
version="1.1"
id="svg977"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs981" />
<path
id="path973"
d="M 10.699219 8.9003906 L 10.699219 9 C 12.199219 11.3 13.800781 13.600391 15.300781 15.900391 L 15.300781 16 C 13.800781 18.3 12.199219 20.600391 10.699219 22.900391 L 10.699219 23 L 14.199219 23 L 14.300781 22.900391 C 15.200781 21.500391 16.199609 20.099219 17.099609 18.699219 C 17.199609 18.599219 17.099219 18.599219 17.199219 18.699219 C 18.099219 20.099219 19.1 21.500391 20 22.900391 L 20.099609 23 L 23.599609 23 C 21.699609 20 19.699219 17.099609 17.699219 14.099609 C 16.499219 12.399609 15.399219 10.600391 14.199219 8.9003906 L 10.699219 8.9003906 z M 6 9 C 7.6 11.3 9.0996094 13.6 10.599609 16 L 10.599609 16.099609 C 9.4996094 17.799609 8.4007813 19.399609 7.3007812 21.099609 C 6.8007813 21.699609 6.4 22.4 6 23 L 6 23.099609 L 9.5 23.099609 C 11.1 20.699609 12.699219 18.399609 14.199219 16.099609 L 14.199219 16 C 13.499219 14.8 12.700391 13.7 11.900391 12.5 C 11.100391 11.4 10.399609 10.199609 9.5996094 9.0996094 L 9.5 9 L 6 9 z M 18.199219 13 L 18.199219 13.099609 C 18.699219 13.899609 19.199219 14.600391 19.699219 15.400391 L 26 15.400391 L 26 13 L 18.199219 13 z M 20.5 16.599609 L 20.5 16.699219 C 21 17.499219 21.5 18.2 22 19 L 26 19 L 26 16.599609 L 20.5 16.599609 z " />
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.679688 3.894531 L 4.679688 3.9375 C 5.335938 4.945312 6.039062 5.949219 6.695312 6.957031 L 6.695312 7 C 6.039062 8.007812 5.335938 9.011719 4.679688 10.019531 L 4.679688 10.0625 L 6.210938 10.0625 L 6.257812 10.019531 C 6.648438 9.40625 7.085938 8.792969 7.480469 8.179688 C 7.523438 8.136719 7.480469 8.136719 7.523438 8.179688 C 7.917969 8.792969 8.355469 9.40625 8.75 10.019531 L 8.792969 10.0625 L 10.324219 10.0625 C 9.492188 8.75 8.617188 7.480469 7.742188 6.167969 C 7.21875 5.425781 6.738281 4.636719 6.210938 3.894531 Z M 2.625 3.9375 C 3.324219 4.945312 3.980469 5.949219 4.636719 7 L 4.636719 7.042969 C 4.15625 7.789062 3.675781 8.488281 3.195312 9.230469 C 2.976562 9.492188 2.800781 9.800781 2.625 10.0625 L 2.625 10.105469 L 4.15625 10.105469 C 4.855469 9.054688 5.554688 8.050781 6.210938 7.042969 L 6.210938 7 C 5.90625 6.476562 5.554688 5.992188 5.207031 5.46875 C 4.855469 4.988281 4.550781 4.460938 4.199219 3.980469 L 4.15625 3.9375 Z M 7.960938 5.6875 L 7.960938 5.730469 C 8.179688 6.082031 8.398438 6.386719 8.617188 6.738281 L 11.375 6.738281 L 11.375 5.6875 Z M 8.96875 7.261719 L 8.96875 7.304688 C 9.1875 7.65625 9.40625 7.960938 9.625 8.3125 L 11.375 8.3125 L 11.375 7.261719 Z M 8.96875 7.261719 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 7.054688 1.691406 C 7.054688 1.691406 6.519531 2.148438 5.972656 2.59375 C 5.410156 2.578125 4.304688 2.710938 3.710938 2.945312 C 3.15625 2.570312 2.671875 2.15625 2.671875 2.15625 C 2.671875 2.15625 2.257812 2.917969 2 3.367188 C 1.613281 3.585938 1.226562 3.832031 0.882812 4.160156 C 0.480469 3.988281 0.015625 3.78125 0 3.777344 C 0.53125 4.921875 0.886719 6.070312 1.863281 6.761719 C 3.410156 4.144531 10.601562 4.386719 12.183594 6.746094 C 13.203125 6.175781 13.597656 4.953125 14 3.820312 C 13.957031 3.835938 13.410156 4.03125 13.058594 4.175781 C 12.84375 3.929688 12.347656 3.550781 12.0625 3.367188 C 11.847656 2.949219 11.628906 2.539062 11.402344 2.128906 C 11.402344 2.128906 10.941406 2.496094 10.402344 2.902344 C 9.675781 2.757812 8.800781 2.585938 8.0625 2.628906 C 7.71875 2.320312 7.382812 2.011719 7.054688 1.691406 Z M 0.550781 6.210938 L 1.828125 9.519531 C 4.046875 12.652344 9.707031 12.867188 12.175781 9.582031 C 12.757812 8.171875 13.546875 6.191406 13.546875 6.191406 C 12.914062 7.199219 11.882812 7.890625 11.246094 8.261719 C 10.796875 8.527344 9.757812 8.6875 9.757812 8.6875 L 7.023438 7.171875 L 4.277344 8.65625 C 4.277344 8.65625 3.25 8.480469 2.785156 8.25 C 1.847656 7.714844 1.214844 7.078125 0.550781 6.210938 Z M 0.550781 6.210938 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -1 +1,6 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="512" height="512"><path d="M170.322 349.808c-2.4-15.66-9-28.38-25.020-34.531-6.27-2.4-11.7-6.78-17.88-9.54-7.020-3.15-14.16-6.15-21.57-8.1-5.61-1.5-10.83 1.020-14.16 5.94-3.15 4.62-0.87 8.97 1.77 12.84 2.97 4.35 6.27 8.49 9.6 12.57 5.52 6.78 11.37 13.29 16.74 20.161 5.13 6.57 9.51 13.86 8.76 22.56-1.65 19.080-10.29 34.891-24.21 47.76-1.53 1.38-4.23 2.37-6.21 2.19-8.88-0.96-16.95-4.32-23.46-10.53-7.47-7.11-6.33-15.48 2.61-20.67 2.13-1.23 4.35-2.37 6.3-3.87 5.46-4.11 7.29-11.13 4.32-17.22-1.41-2.94-3-6.12-5.34-8.25-11.43-10.41-22.651-21.151-34.891-30.63-29.671-23.041-44.91-53.52-47.251-90.421-2.64-40.981 6.87-79.231 28.5-114.242 8.19-13.29 17.73-25.951 32.37-32.52 9.96-4.47 20.88-6.99 31.531-9.78 29.311-7.71 58.89-13.5 89.401-8.34 26.28 4.41 45.511 17.94 54.331 43.77 5.79 16.89 7.17 34.35 5.37 52.231-3.54 35.131-29.49 66.541-63.331 75.841-14.67 4.020-22.68 1.77-31.5-10.44-6.33-8.79-11.58-18.36-17.25-27.631-0.84-1.38-1.44-2.97-2.16-4.44-0.69-1.47-1.44-2.88-2.16-4.35 2.13 15.24 5.67 29.911 13.98 42.99 4.5 7.11 10.5 12.36 19.29 13.14 32.34 2.91 59.641-7.71 79.021-33.721 21.69-29.101 26.461-62.581 20.19-97.831-1.23-6.96-3.3-13.77-4.77-20.7-0.99-4.47 0.78-7.77 5.19-9.33 2.040-0.69 4.14-1.26 6.18-1.68 26.461-5.7 53.221-7.59 80.191-4.86 30.601 3.060 59.551 11.46 85.441 28.471 40.531 26.67 65.641 64.621 79.291 110.522 1.98 6.66 2.28 13.95 2.46 20.971 0.12 4.68-2.88 5.91-6.45 2.97-3.93-3.21-7.53-6.87-10.92-10.65-3.15-3.57-5.67-7.65-8.73-11.4-2.37-2.94-4.44-2.49-5.58 1.17-0.72 2.22-1.35 4.41-1.98 6.63-7.080 25.26-18.24 48.3-36.33 67.711-2.52 2.73-4.77 6.78-5.070 10.38-0.78 9.96-1.35 20.13-0.39 30.060 1.98 21.331 5.070 42.57 7.47 63.871 1.35 12.030-2.52 19.11-13.83 23.281-7.95 2.91-16.47 5.040-24.87 5.64-13.38 0.93-26.88 0.27-40.32 0.27-0.36-15 0.93-29.731-13.17-37.771 2.73-11.13 5.88-21.69 7.77-32.49 1.56-8.97 0.24-17.79-6.060-25.14-5.91-6.93-13.32-8.82-20.101-4.86-20.43 11.91-41.671 11.97-63.301 4.17-9.93-3.6-16.86-1.56-22.351 7.5-5.91 9.75-8.4 20.7-7.74 31.771 0.84 13.95 3.27 27.75 5.13 41.64 1.020 7.77 0.15 9.78-7.56 11.76-17.13 4.35-34.56 4.83-52.081 3.42-0.93-0.090-1.86-0.48-2.46-0.63-0.87-14.55 0.66-29.671-16.68-37.411 7.68-16.29 6.63-33.18 3.99-50.070l-0.060-0.15zM66.761 292.718c2.55-2.4 4.59-6.15 5.31-9.6 1.8-8.64-4.68-20.22-12.18-23.43-3.99-1.74-7.47-1.11-10.29 2.070-6.87 7.77-13.65 15.63-20.401 23.521-1.14 1.35-2.16 2.94-2.97 4.53-2.7 5.19-1.11 8.97 4.65 10.38 3.48 0.87 7.080 1.050 10.65 1.56 9.3-0.9 18.3-2.46 25.23-9v-0.030zM67.541 206.347c-0.030-6.18-5.19-11.34-11.28-11.37-6.27-0.030-11.67 5.58-11.46 11.76 0.27 6.21 5.43 11.19 11.61 11.070 6.24-0.090 11.22-5.19 11.16-11.43l-0.030-0.030z"></path></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 4.65625 9.566406 C 4.589844 9.136719 4.410156 8.789062 3.972656 8.621094 C 3.800781 8.554688 3.652344 8.433594 3.484375 8.359375 C 3.292969 8.273438 3.097656 8.191406 2.894531 8.136719 C 2.742188 8.097656 2.597656 8.167969 2.507812 8.300781 C 2.421875 8.425781 2.484375 8.546875 2.554688 8.652344 C 2.636719 8.769531 2.726562 8.882812 2.816406 8.996094 C 2.96875 9.179688 3.128906 9.359375 3.277344 9.546875 C 3.417969 9.726562 3.535156 9.925781 3.515625 10.164062 C 3.46875 10.6875 3.234375 11.117188 2.851562 11.46875 C 2.8125 11.507812 2.738281 11.535156 2.683594 11.53125 C 2.441406 11.503906 2.21875 11.410156 2.042969 11.242188 C 1.835938 11.046875 1.867188 10.820312 2.113281 10.675781 C 2.171875 10.644531 2.230469 10.613281 2.285156 10.570312 C 2.433594 10.457031 2.484375 10.265625 2.402344 10.101562 C 2.367188 10.019531 2.320312 9.933594 2.257812 9.875 C 1.945312 9.589844 1.636719 9.296875 1.304688 9.035156 C 0.492188 8.40625 0.0742188 7.574219 0.0117188 6.5625 C -0.0585938 5.445312 0.199219 4.398438 0.792969 3.441406 C 1.015625 3.078125 1.277344 2.730469 1.675781 2.550781 C 1.949219 2.429688 2.246094 2.359375 2.539062 2.285156 C 3.339844 2.074219 4.148438 1.914062 4.984375 2.054688 C 5.703125 2.175781 6.226562 2.546875 6.46875 3.253906 C 6.625 3.714844 6.664062 4.191406 6.617188 4.679688 C 6.519531 5.640625 5.808594 6.5 4.882812 6.753906 C 4.484375 6.863281 4.261719 6.804688 4.023438 6.46875 C 3.847656 6.230469 3.707031 5.96875 3.550781 5.714844 C 3.527344 5.675781 3.511719 5.632812 3.492188 5.59375 C 3.472656 5.550781 3.453125 5.511719 3.433594 5.472656 C 3.492188 5.890625 3.585938 6.292969 3.816406 6.648438 C 3.9375 6.84375 4.101562 6.988281 4.34375 7.007812 C 5.226562 7.085938 5.972656 6.796875 6.503906 6.085938 C 7.097656 5.289062 7.226562 4.375 7.054688 3.410156 C 7.019531 3.21875 6.964844 3.035156 6.925781 2.84375 C 6.898438 2.722656 6.945312 2.632812 7.066406 2.589844 C 7.121094 2.570312 7.179688 2.554688 7.234375 2.542969 C 7.960938 2.386719 8.691406 2.335938 9.429688 2.410156 C 10.265625 2.496094 11.054688 2.722656 11.765625 3.191406 C 12.871094 3.917969 13.558594 4.957031 13.933594 6.210938 C 13.988281 6.394531 13.996094 6.59375 14 6.785156 C 14.003906 6.914062 13.921875 6.945312 13.824219 6.867188 C 13.714844 6.777344 13.617188 6.679688 13.523438 6.574219 C 13.4375 6.476562 13.371094 6.367188 13.285156 6.261719 C 13.222656 6.183594 13.164062 6.195312 13.132812 6.296875 C 13.113281 6.355469 13.097656 6.414062 13.078125 6.476562 C 12.886719 7.167969 12.582031 7.796875 12.085938 8.328125 C 12.015625 8.402344 11.957031 8.511719 11.949219 8.613281 C 11.925781 8.882812 11.910156 9.164062 11.9375 9.433594 C 11.992188 10.015625 12.074219 10.597656 12.140625 11.179688 C 12.179688 11.507812 12.070312 11.703125 11.761719 11.816406 C 11.546875 11.894531 11.3125 11.953125 11.082031 11.972656 C 10.71875 11.996094 10.347656 11.976562 9.980469 11.976562 C 9.96875 11.566406 10.003906 11.164062 9.621094 10.945312 C 9.695312 10.640625 9.78125 10.351562 9.832031 10.058594 C 9.875 9.8125 9.839844 9.570312 9.667969 9.371094 C 9.503906 9.179688 9.304688 9.128906 9.117188 9.238281 C 8.558594 9.5625 7.976562 9.5625 7.386719 9.351562 C 7.113281 9.253906 6.925781 9.308594 6.773438 9.554688 C 6.613281 9.824219 6.546875 10.121094 6.5625 10.425781 C 6.585938 10.804688 6.652344 11.183594 6.703125 11.5625 C 6.730469 11.777344 6.707031 11.832031 6.496094 11.886719 C 6.027344 12.003906 5.550781 12.015625 5.074219 11.976562 C 5.046875 11.976562 5.023438 11.964844 5.007812 11.960938 C 4.980469 11.5625 5.023438 11.148438 4.550781 10.9375 C 4.761719 10.492188 4.730469 10.03125 4.660156 9.570312 Z M 1.824219 8.003906 C 1.894531 7.9375 1.949219 7.835938 1.96875 7.742188 C 2.019531 7.503906 1.84375 7.1875 1.636719 7.101562 C 1.527344 7.054688 1.433594 7.070312 1.355469 7.15625 C 1.167969 7.371094 0.984375 7.585938 0.796875 7.800781 C 0.765625 7.835938 0.738281 7.882812 0.71875 7.925781 C 0.644531 8.066406 0.6875 8.167969 0.84375 8.207031 C 0.941406 8.230469 1.039062 8.238281 1.136719 8.25 C 1.390625 8.226562 1.636719 8.183594 1.824219 8.003906 Z M 1.847656 5.640625 C 1.847656 5.472656 1.703125 5.332031 1.539062 5.332031 C 1.367188 5.332031 1.21875 5.484375 1.226562 5.652344 C 1.230469 5.824219 1.375 5.957031 1.542969 5.957031 C 1.714844 5.953125 1.847656 5.8125 1.847656 5.644531 Z M 1.847656 5.640625 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 11.214844 0.015625 C 11.214844 1.125 5.859375 1.976562 2.785156 2.179688 L 2.785156 5.335938 C 4.082031 5.421875 5.789062 5.628906 7.324219 5.917969 C 5.789062 6.207031 4.082031 6.410156 2.785156 6.492188 L 2.785156 9.648438 C 4.082031 9.734375 5.785156 9.9375 7.320312 10.226562 C 5.785156 10.515625 4.082031 10.71875 2.785156 10.804688 L 2.785156 13.988281 L 4.308594 13.765625 C 7.152344 13.339844 10.285156 12.53125 11.214844 11.820312 L 11.214844 8.632812 C 11.214844 8.433594 10.851562 8.21875 10.265625 8 C 10.675781 7.832031 11.003906 7.667969 11.214844 7.507812 L 11.214844 4.324219 C 11.214844 4.125 10.855469 3.90625 10.277344 3.6875 C 10.683594 3.523438 11.003906 3.355469 11.214844 3.195312 Z M 10.269531 8.339844 C 10.6875 8.503906 10.839844 8.617188 10.890625 8.664062 C 10.855469 8.734375 10.726562 8.871094 10.359375 9.050781 C 10.300781 9.078125 10.238281 9.105469 10.183594 9.128906 C 9.550781 9.402344 8.570312 9.667969 7.320312 9.90625 C 6.707031 9.792969 6.046875 9.6875 5.382812 9.597656 C 7.25 9.261719 9.09375 8.796875 10.269531 8.339844 Z M 10.277344 4.027344 C 10.691406 4.195312 10.839844 4.308594 10.890625 4.355469 C 10.863281 4.40625 10.78125 4.5 10.578125 4.625 L 10.574219 4.625 C 10.535156 4.648438 10.488281 4.675781 10.441406 4.699219 C 9.839844 5.011719 8.765625 5.324219 7.335938 5.59375 C 6.71875 5.480469 6.058594 5.375 5.394531 5.285156 C 7.269531 4.953125 9.097656 4.492188 10.277344 4.027344 Z M 10.277344 4.027344 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.5 20.821429 10.241071 19.5625 L 11.133929 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.607143 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.294643 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
<path style="fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(100%,100%,100%);stroke-opacity:1;stroke-miterlimit:4;" d="M 21.946429 2.875 C 20.375 3.133929 18.973214 3.991071 18.017857 5.258929 L 18.017857 5.267857 L 17.392857 6.321429 L 17.339286 6.419643 L 17.294643 6.321429 L 17 5.544643 C 14.928571 7.625 13.366071 9.955357 13.419643 12.267857 L 13.419643 12.375 L 13.339286 12.3125 L 12.598214 11.821429 C 11.571429 14.669643 10.517857 16.848214 11.196429 19.875 L 11.223214 19.973214 L 11.125 19.9375 L 10.241071 19.5625 C 10.241071 19.580357 10.25 19.598214 10.25 19.616071 C 10.517857 20.839286 11.294643 21.901786 12.375 22.544643 C 12.428571 22.160714 12.482143 21.776786 12.544643 21.383929 C 11 17.767857 12.348214 15.107143 12.955357 12.723214 L 13.892857 13.258929 C 13.758929 11.026786 15.080357 8.607143 16.785714 6.508929 L 17.285714 7.375 C 18.553571 4.767857 19.5625 3.723214 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
<path style="fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke:rgb(0%,0%,0%);stroke-opacity:1;stroke-miterlimit:4;" d="M 22.517857 2 L 22.464286 2.008929 C 20.383929 2.375 18.339286 3.133929 17.446429 4.973214 L 17.071429 4.3125 L 17.035714 4.25 L 16.991071 4.303571 C 15.883929 5.357143 14.892857 6.526786 14.044643 7.803571 C 13.339286 8.723214 12.919643 9.839286 12.839286 11 L 12.303571 10.339286 L 12.25 10.267857 L 12.214286 10.348214 C 11.508929 11.857143 10.9375 13.428571 10.517857 15.044643 C 10.116071 16.25 10.0625 17.535714 10.366071 18.767857 L 9.482143 18.258929 L 9.410714 18.214286 L 9.401786 18.294643 C 9.223214 20.151786 9.982143 21.973214 11.419643 23.142857 L 10.455357 23.383929 L 10.25 23.428571 L 10.455357 23.482143 C 10.973214 23.598214 11.464286 23.794643 11.901786 24.089286 C 12.3125 24.383929 12.508929 24.892857 12.419643 25.383929 L 12.419643 28.098214 L 12.428571 28.116071 L 13.651786 29.857143 L 13.75 30 L 13.75 25.723214 C 13.839286 25.178571 14.053571 24.678571 14.366071 24.232143 C 14.660714 23.910714 15.071429 23.723214 15.5 23.696429 L 15.678571 23.678571 L 15.517857 23.598214 L 14.875 23.303571 C 16.714286 22.035714 18.035714 20.142857 18.571429 17.982143 L 18.589286 17.892857 L 18.508929 17.919643 L 17.714286 18.133929 C 18.607143 17.098214 19.3125 15.910714 19.803571 14.633929 C 20.508929 13 21.178571 11.169643 21.732143 9.696429 L 21.758929 9.625 L 21.678571 9.625 L 21.0625 9.669643 C 21.857143 8.651786 22.339286 7.428571 22.446429 6.142857 C 22.633929 4.785714 22.660714 3.419643 22.526786 2.053571 Z M 21.946429 2.875 C 21.982143 5.348214 21.910714 7.785714 19.776786 10.107143 L 19.696429 10.196429 L 19.8125 10.196429 L 20.6875 10.205357 C 19.267857 13.160714 18.348214 16.098214 16.303571 19.035714 L 16.232143 19.142857 L 16.357143 19.125 L 17.4375 18.919643 C 16.883929 20.598214 15.607143 21.946429 13.955357 22.571429 C 13.5625 17.116071 16.285714 12.303571 18.598214 7.5 L 18.607143 7.491071 L 18.517857 7.428571 C 14.732143 11.660714 13.026786 17.625 12.383929 22.553571 C 11.285714 21.901786 10.508929 20.821429 10.241071 19.5625 L 11.142857 19.946429 L 11.232143 19.982143 L 11.214286 19.883929 C 10.526786 16.857143 11.589286 14.678571 12.616071 11.830357 L 13.348214 12.321429 L 13.4375 12.383929 L 13.4375 12.276786 C 13.375 9.964286 14.9375 7.633929 17.008929 5.553571 L 17.303571 6.321429 L 17.339286 6.419643 L 17.392857 6.321429 L 18.026786 5.267857 C 18.973214 3.991071 20.375 3.133929 21.946429 2.875 Z M 21.946429 2.875 " transform="matrix(0.4375,0,0,0.4375,0,0)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -1,6 +1,9 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 8.01531L19.2375 12.1073V20.2894L12.5066 16.1992V8.01531Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0294 12.1073V20.2894L27.1563 16.1992V8.01531L20.0294 12.1073Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.58781 3.66V11.5787L11.7147 15.5381V7.61937L4.58781 3.66Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5066 25.04L19.2375 29V21.1348V21.0818L12.5066 17.1219V25.04Z" fill="black"/>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14px" height="14px" viewBox="0 0 14 14" version="1.1">
<g id="surface1">
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 3.507812 L 8.417969 5.296875 L 8.417969 8.875 L 5.472656 7.085938 Z M 5.472656 3.507812 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 8.761719 5.296875 L 8.761719 8.875 L 11.882812 7.085938 L 11.882812 3.507812 Z M 8.761719 5.296875 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2.007812 1.601562 L 2.007812 5.066406 L 5.125 6.796875 L 5.125 3.332031 Z M 2.007812 1.601562 "/>
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 5.472656 10.953125 L 8.417969 12.6875 L 8.417969 9.222656 L 5.472656 7.492188 Z M 5.472656 10.953125 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 620 B

After

Width:  |  Height:  |  Size: 961 B

View File

@@ -16,6 +16,7 @@
"ctrl-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"shift-enter": "menu::UseSelectedQuery",
"ctrl-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
"ctrl-o": "workspace::Open",
@@ -72,7 +73,7 @@
"ctrl-n": "editor::MoveDown",
"ctrl-b": "editor::MoveLeft",
"ctrl-f": "editor::MoveRight",
"ctrl-shift-l": "editor::NextScreen", // todo!(linux): What is this
"ctrl-shift-l": "editor::NextScreen", // todo(linux): What is this
"alt-left": "editor::MoveToPreviousWordStart",
"alt-b": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd",
@@ -387,7 +388,7 @@
}
},
// Bindings from Sublime Text
// todo!(linux) make sure these match linux bindings or remove above comment?
// todo(linux) make sure these match linux bindings or remove above comment?
{
"context": "Editor",
"bindings": {
@@ -411,7 +412,7 @@
}
},
// Bindings from Atom
// todo!(linux) make sure these match linux bindings or remove above comment?
// todo(linux) make sure these match linux bindings or remove above comment?
{
"context": "Pane",
"bindings": {
@@ -466,6 +467,7 @@
"context": "Editor && mode == full",
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"ctrl-k enter": "editor::OpenExcerptsSplit",
"ctrl-f8": "editor::GoToHunk",
"ctrl-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"

View File

@@ -17,6 +17,7 @@
"cmd-enter": "menu::SecondaryConfirm",
"escape": "menu::Cancel",
"ctrl-c": "menu::Cancel",
"shift-enter": "menu::UseSelectedQuery",
"cmd-shift-w": "workspace::CloseWindow",
"shift-escape": "workspace::ToggleZoom",
"cmd-o": "workspace::Open",
@@ -48,6 +49,7 @@
"cmd-backspace": "editor::DeleteToBeginningOfLine",
"cmd-delete": "editor::DeleteToEndOfLine",
"alt-backspace": "editor::DeleteToPreviousWordStart",
"ctrl-w": "editor::DeleteToPreviousWordStart",
"alt-delete": "editor::DeleteToNextWordEnd",
"alt-h": "editor::DeleteToPreviousWordStart",
"alt-d": "editor::DeleteToNextWordEnd",
@@ -506,6 +508,7 @@
"context": "Editor && mode == full",
"bindings": {
"alt-enter": "editor::OpenExcerpts",
"cmd-k enter": "editor::OpenExcerptsSplit",
"cmd-f8": "editor::GoToHunk",
"cmd-shift-f8": "editor::GoToPrevHunk",
"ctrl-enter": "assistant::InlineAssist"

View File

@@ -288,6 +288,13 @@
"ctrl-w ctrl-o": "workspace::CloseInactiveTabsAndPanes",
"ctrl-w n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w ctrl-n": ["workspace::NewFileInDirection", "Up"],
"ctrl-w d": "editor::GoToDefinitionSplit",
"ctrl-w g d": "editor::GoToDefinitionSplit",
"ctrl-w shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w g shift-d": "editor::GoToTypeDefinitionSplit",
"ctrl-w space": "editor::OpenExcerptsSplit",
"ctrl-w g space": "editor::OpenExcerptsSplit",
"-": "pane::RevealInProjectPanel"
}
},

View File

@@ -97,7 +97,7 @@ impl ActivityIndicator {
cx,
);
});
workspace.add_item(
workspace.add_item_to_active_pane(
Box::new(
cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx)),
),

View File

@@ -102,8 +102,9 @@ pub struct OpenAiResponseStreamEvent {
pub usage: Option<OpenAiUsage>,
}
pub async fn stream_completion(
async fn stream_completion(
api_url: String,
kind: OpenAiCompletionProviderKind,
credential: ProviderCredential,
executor: BackgroundExecutor,
request: Box<dyn CompletionRequest>,
@@ -117,10 +118,11 @@ pub async fn stream_completion(
let (tx, rx) = futures::channel::mpsc::unbounded::<Result<OpenAiResponseStreamEvent>>();
let (auth_header_name, auth_header_value) = kind.auth_header(api_key);
let json_data = request.data()?;
let mut response = Request::post(format!("{api_url}/chat/completions"))
let mut response = Request::post(kind.completions_endpoint_url(&api_url))
.header("Content-Type", "application/json")
.header("Authorization", format!("Bearer {}", api_key))
.header(auth_header_name, auth_header_value)
.body(json_data)?
.send_async()
.await?;
@@ -194,22 +196,65 @@ pub async fn stream_completion(
}
}
#[derive(Clone)]
pub enum OpenAiCompletionProviderKind {
OpenAi,
AzureOpenAi {
deployment_id: String,
api_version: String,
},
}
impl OpenAiCompletionProviderKind {
/// Returns the chat completion endpoint URL for this [`OpenAiCompletionProviderKind`].
fn completions_endpoint_url(&self, api_url: &str) -> String {
match self {
Self::OpenAi => {
// https://platform.openai.com/docs/api-reference/chat/create
format!("{api_url}/chat/completions")
}
Self::AzureOpenAi {
deployment_id,
api_version,
} => {
// https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#completions
format!("{api_url}/openai/deployments/{deployment_id}/completions?api-version={api_version}")
}
}
}
/// Returns the authentication header for this [`OpenAiCompletionProviderKind`].
fn auth_header(&self, api_key: String) -> (&'static str, String) {
match self {
Self::OpenAi => ("Authorization", format!("Bearer {api_key}")),
Self::AzureOpenAi { .. } => ("Api-Key", api_key),
}
}
}
#[derive(Clone)]
pub struct OpenAiCompletionProvider {
api_url: String,
kind: OpenAiCompletionProviderKind,
model: OpenAiLanguageModel,
credential: Arc<RwLock<ProviderCredential>>,
executor: BackgroundExecutor,
}
impl OpenAiCompletionProvider {
pub async fn new(api_url: String, model_name: String, executor: BackgroundExecutor) -> Self {
pub async fn new(
api_url: String,
kind: OpenAiCompletionProviderKind,
model_name: String,
executor: BackgroundExecutor,
) -> Self {
let model = executor
.spawn(async move { OpenAiLanguageModel::load(&model_name) })
.await;
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
Self {
api_url,
kind,
model,
credential,
executor,
@@ -297,6 +342,7 @@ impl CompletionProvider for OpenAiCompletionProvider {
let model: Box<dyn LanguageModel> = Box::new(self.model.clone());
model
}
fn complete(
&self,
prompt: Box<dyn CompletionRequest>,
@@ -307,7 +353,8 @@ impl CompletionProvider for OpenAiCompletionProvider {
// At some point in the future we should rectify this.
let credential = self.credential.read().clone();
let api_url = self.api_url.clone();
let request = stream_completion(api_url, credential, self.executor.clone(), prompt);
let kind = self.kind.clone();
let request = stream_completion(api_url, kind, credential, self.executor.clone(), prompt);
async move {
let response = request.await?;
let stream = response
@@ -322,6 +369,7 @@ impl CompletionProvider for OpenAiCompletionProvider {
}
.boxed()
}
fn box_clone(&self) -> Box<dyn CompletionProvider> {
Box::new((*self).clone())
}

View File

@@ -7,11 +7,13 @@ use crate::{
SavedMessage, Split, ToggleFocus, ToggleIncludeConversation, ToggleRetrieveContext,
};
use ai::prompts::repository_context::PromptCodeSnippet;
use ai::providers::open_ai::OPEN_AI_API_URL;
use ai::{
auth::ProviderCredential,
completion::{CompletionProvider, CompletionRequest},
providers::open_ai::{OpenAiCompletionProvider, OpenAiRequest, RequestMessage},
providers::open_ai::{
OpenAiCompletionProvider, OpenAiCompletionProviderKind, OpenAiRequest, RequestMessage,
OPEN_AI_API_URL,
},
};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Local};
@@ -29,9 +31,9 @@ use fs::Fs;
use futures::StreamExt;
use gpui::{
canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AppContext,
AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, EventEmitter,
FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement,
IntoElement, Model, ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context, EventEmitter, FocusHandle,
FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
ModelContext, ParentElement, Pixels, PromptLevel, Render, SharedString,
StatefulInteractiveElement, Styled, Subscription, Task, TextStyle, UniformListScrollHandle,
View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace, WindowContext,
};
@@ -131,6 +133,7 @@ impl AssistantPanel {
})?;
let completion_provider = OpenAiCompletionProvider::new(
api_url,
OpenAiCompletionProviderKind::OpenAi,
model_name,
cx.background_executor().clone(),
)
@@ -771,7 +774,7 @@ impl AssistantPanel {
} else {
editor.highlight_background::<PendingInlineAssist>(
background_ranges,
|theme| theme.editor_active_line_background, // todo!("use the appropriate color")
|theme| theme.editor_active_line_background, // todo("use the appropriate color")
cx,
);
}
@@ -1274,25 +1277,25 @@ impl Render for AssistantPanel {
let view = cx.view().clone();
let scroll_handle = self.saved_conversations_scroll_handle.clone();
let conversation_count = self.saved_conversations.len();
canvas(move |bounds, cx| {
uniform_list(
view,
"saved_conversations",
conversation_count,
|this, range, cx| {
range
.map(|ix| this.render_saved_conversation(ix, cx))
.collect()
},
)
.track_scroll(scroll_handle)
.into_any_element()
.draw(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
);
})
canvas(
move |bounds, cx| {
let mut list = uniform_list(
view,
"saved_conversations",
conversation_count,
|this, range, cx| {
range
.map(|ix| this.render_saved_conversation(ix, cx))
.collect()
},
)
.track_scroll(scroll_handle)
.into_any_element();
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_bounds, mut list, cx| list.paint(cx),
)
.size_full()
.into_any_element()
}),
@@ -1533,6 +1536,7 @@ impl Conversation {
api_url
.clone()
.unwrap_or_else(|| OPEN_AI_API_URL.to_string()),
OpenAiCompletionProviderKind::OpenAi,
model.full_name().into(),
cx.background_executor().clone(),
)

View File

@@ -20,7 +20,7 @@ use smol::io::AsyncReadExt;
use settings::{Settings, SettingsStore};
use smol::{fs::File, process::Command};
use release_channel::{AppCommitSha, ReleaseChannel};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
use std::{
env::consts::{ARCH, OS},
ffi::OsString,
@@ -190,7 +190,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut AppContext) -> Option<(
fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
let release_channel = ReleaseChannel::global(cx);
let version = env!("CARGO_PKG_VERSION");
let version = AppVersion::global(cx).to_string();
let client = client::Client::global(cx).http_client();
let url = client.build_url(&format!(
@@ -242,7 +242,7 @@ fn view_release_notes_locally(workspace: &mut Workspace, cx: &mut ViewContext<Wo
Some(tab_description),
cx,
);
workspace.add_item(Box::new(view.clone()), cx);
workspace.add_item_to_active_pane(Box::new(view.clone()), cx);
cx.notify();
})
.log_err();

View File

@@ -44,7 +44,7 @@ impl Render for UpdateNotification {
crate::view_release_notes(&Default::default(), cx);
this.dismiss(&menu::Cancel, cx)
})),
);
)
}
}

View File

@@ -156,7 +156,7 @@ mod linux {
}
}
// todo!("windows")
// todo("windows")
#[cfg(target_os = "windows")]
mod windows {
use std::path::Path;

View File

@@ -130,7 +130,7 @@ async fn main() -> Result<()> {
})
.await?;
// todo!("windows")
// todo("windows")
#[cfg(windows)]
unimplemented!();
}

View File

@@ -310,7 +310,7 @@ async fn test_basic_following(
let multibuffer_editor_a = workspace_a.update(cx_a, |workspace, cx| {
let editor =
cx.new_view(|cx| Editor::for_multibuffer(multibuffer_a, Some(project_a.clone()), cx));
workspace.add_item(Box::new(editor.clone()), cx);
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
editor
});
executor.run_until_parked();

View File

@@ -204,7 +204,7 @@ impl ChatPanel {
let panel = Self::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|r| r.round());
cx.notify();
});
}
@@ -551,7 +551,6 @@ impl ChatPanel {
.child(
div()
.absolute()
.z_index(1)
.right_0()
.w_6()
.bg(background)
@@ -788,7 +787,7 @@ impl Render for ChatPanel {
.size_full()
.on_action(cx.listener(Self::send))
.child(
h_flex().z_index(1).child(
h_flex().child(
TabBar::new("chat_header").child(
h_flex()
.w_full()

View File

@@ -327,7 +327,7 @@ impl CollabPanel {
let panel = CollabPanel::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|w| w.round());
panel.collapsed_channels = serialized_panel
.collapsed_channels
.unwrap_or_else(|| Vec::new())
@@ -993,7 +993,6 @@ impl CollabPanel {
.children(has_channel_buffer_changed.then(|| {
div()
.w_1p5()
.z_index(1)
.absolute()
.right(px(2.))
.top(px(2.))
@@ -1026,7 +1025,6 @@ impl CollabPanel {
.children(has_messages_notification.then(|| {
div()
.w_1p5()
.z_index(1)
.absolute()
.right(px(2.))
.top(px(4.))
@@ -1052,7 +1050,7 @@ impl CollabPanel {
.indent_step_size(px(20.))
.selected(is_selected)
.on_click(cx.listener(move |_this, _, _cx| {
// todo!()
// todo()
}))
.start_slot(
h_flex()
@@ -1531,7 +1529,7 @@ impl CollabPanel {
id: _id,
name: _name,
} => {
// todo!()
// todo()
}
ListEntry::OutgoingRequest(_) => {}
@@ -2614,7 +2612,6 @@ impl CollabPanel {
.children(has_notes_notification.then(|| {
div()
.w_1p5()
.z_index(1)
.absolute()
.right(px(-1.))
.top(px(-1.))
@@ -2629,49 +2626,44 @@ impl CollabPanel {
),
)
.child(
h_flex()
.absolute()
.right(rems(0.))
.z_index(1)
.h_full()
.child(
h_flex()
.h_full()
.gap_1()
.px_1()
.child(
IconButton::new("channel_chat", IconName::MessageBubbles)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_messages_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.join_channel_chat(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
.visible_on_hover(""),
)
.child(
IconButton::new("channel_notes", IconName::File)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_notes_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.open_channel_notes(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
.visible_on_hover(""),
),
),
h_flex().absolute().right(rems(0.)).h_full().child(
h_flex()
.h_full()
.gap_1()
.px_1()
.child(
IconButton::new("channel_chat", IconName::MessageBubbles)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_messages_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.join_channel_chat(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel chat", cx))
.visible_on_hover(""),
)
.child(
IconButton::new("channel_notes", IconName::File)
.style(ButtonStyle::Filled)
.shape(ui::IconButtonShape::Square)
.icon_size(IconSize::Small)
.icon_color(if has_notes_notification {
Color::Default
} else {
Color::Muted
})
.on_click(cx.listener(move |this, _, cx| {
this.open_channel_notes(channel_id, cx)
}))
.tooltip(|cx| Tooltip::text("Open channel notes", cx))
.visible_on_hover(""),
),
),
)
.tooltip({
let channel_store = self.channel_store.clone();
@@ -2717,31 +2709,34 @@ fn render_tree_branch(is_last: bool, overdraw: bool, cx: &mut WindowContext) ->
let thickness = px(1.);
let color = cx.theme().colors().text;
canvas(move |bounds, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
let right = bounds.right();
let top = bounds.top();
canvas(
|_, _| {},
move |bounds, _, cx| {
let start_x = (bounds.left() + bounds.right() - thickness) / 2.;
let start_y = (bounds.top() + bounds.bottom() - thickness) / 2.;
let right = bounds.right();
let top = bounds.top();
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
cx.paint_quad(fill(
Bounds::from_corners(
point(start_x, top),
point(
start_x + thickness,
if is_last {
start_y
} else {
bounds.bottom() + if overdraw { px(1.) } else { px(0.) }
},
),
),
),
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
})
color,
));
cx.paint_quad(fill(
Bounds::from_corners(point(start_x, start_y), point(right, start_y + thickness)),
color,
));
},
)
.w(width)
.h(line_height)
}

View File

@@ -329,24 +329,27 @@ impl Render for CollabTitlebarItem {
}
}
fn render_color_ribbon(color: Hsla) -> gpui::Canvas {
canvas(move |bounds, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
})
fn render_color_ribbon(color: Hsla) -> impl Element {
canvas(
move |_, _| {},
move |bounds, _, cx| {
let height = bounds.size.height;
let horizontal_offset = height;
let vertical_offset = px(height.0 / 2.0);
let mut path = Path::new(bounds.lower_left());
path.curve_to(
bounds.origin + point(horizontal_offset, vertical_offset),
bounds.origin + point(px(0.0), vertical_offset),
);
path.line_to(bounds.upper_right() + point(-horizontal_offset, vertical_offset));
path.curve_to(
bounds.lower_right(),
bounds.upper_right() + point(px(0.0), vertical_offset),
);
path.line_to(bounds.lower_left());
cx.paint_path(path, color);
},
)
.h_1()
.w_full()
}

View File

@@ -27,10 +27,7 @@ impl RenderOnce for FacePile {
let player_list = self.faces.into_iter().enumerate().map(|(ix, player)| {
let isnt_last = ix < player_count - 1;
div()
.z_index((player_count - ix) as u16)
.when(isnt_last, |div| div.neg_mr_1())
.child(player)
div().when(isnt_last, |div| div.neg_mr_1()).child(player)
});
self.base.children(player_list)
}

View File

@@ -183,7 +183,7 @@ impl NotificationPanel {
let panel = Self::new(workspace, cx);
if let Some(serialized_panel) = serialized_panel {
panel.update(cx, |panel, cx| {
panel.width = serialized_panel.width;
panel.width = serialized_panel.width.map(|w| w.round());
cx.notify();
});
}

View File

@@ -197,7 +197,7 @@ pub struct ColorStates {
/// Returns a set of colors for different states of an element.
///
/// todo!("This should take a theme and use appropriate colors from it")
/// todo("This should take a theme and use appropriate colors from it")
pub fn states_for_color(color: RGBAColor, is_light: bool) -> ColorStates {
let adjustment_factor = if is_light { 0.1 } else { -0.1 };
let hover_adjustment = 1.0 - adjustment_factor;

View File

@@ -37,6 +37,24 @@ pub struct CommandPalette {
picker: View<Picker<CommandPaletteDelegate>>,
}
fn trim_consecutive_whitespaces(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut last_char_was_whitespace = false;
for char in input.trim().chars() {
if char.is_whitespace() {
if !last_char_was_whitespace {
result.push(char);
}
last_char_was_whitespace = true;
} else {
result.push(char);
last_char_was_whitespace = false;
}
}
result
}
impl CommandPalette {
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &Toggle, cx| {
@@ -247,7 +265,7 @@ impl PickerDelegate for CommandPaletteDelegate {
let mut commands = self.all_commands.clone();
let hit_counts = cx.global::<HitCounts>().clone();
let executor = cx.background_executor().clone();
let query = query.clone();
let query = trim_consecutive_whitespaces(&query.as_str());
async move {
commands.sort_by_key(|action| {
(
@@ -265,7 +283,6 @@ impl PickerDelegate for CommandPaletteDelegate {
char_bag: command.name.chars().collect(),
})
.collect::<Vec<_>>();
let matches = if query.is_empty() {
candidates
.into_iter()
@@ -468,7 +485,7 @@ mod tests {
});
workspace.update(cx, |workspace, cx| {
workspace.add_item(Box::new(editor.clone()), cx);
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
editor.update(cx, |editor, cx| editor.focus(cx))
});

View File

@@ -202,7 +202,7 @@ impl ProjectDiagnosticsEditor {
let diagnostics = cx.new_view(|cx| {
ProjectDiagnosticsEditor::new(workspace.project().clone(), workspace_handle, cx)
});
workspace.add_item(Box::new(diagnostics), cx);
workspace.add_item_to_active_pane(Box::new(diagnostics), cx);
}
}
@@ -1593,20 +1593,18 @@ mod tests {
let name: SharedString = match block {
TransformBlock::Custom(block) => cx.with_element_context({
|cx| -> Option<SharedString> {
block
.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
editor_style: &editor::EditorStyle::default(),
})
.inner_id()?
.try_into()
.ok()
let mut element = block.render(&mut BlockContext {
context: cx,
anchor_x: px(0.),
gutter_dimensions: &GutterDimensions::default(),
line_height: px(0.),
em_width: px(0.),
max_width: px(0.),
block_id: ix,
editor_style: &editor::EditorStyle::default(),
});
let element = element.downcast_mut::<Div>().unwrap();
element.interactivity().element_id.clone()?.try_into().ok()
}
})?,

View File

@@ -197,6 +197,7 @@ gpui::actions!(
NewlineBelow,
NextScreen,
OpenExcerpts,
OpenExcerptsSplit,
OpenPermalinkToLine,
Outdent,
PageDown,

View File

@@ -28,7 +28,10 @@ use crate::{hover_links::InlayHighlight, movement::TextLayoutDetails, InlayId};
pub use block_map::{BlockMap, BlockPoint};
use collections::{BTreeMap, HashMap, HashSet};
use fold_map::FoldMap;
use gpui::{Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle};
use gpui::{
Font, HighlightStyle, Hsla, LineLayout, Model, ModelContext, Pixels, UnderlineStyle,
WindowContext,
};
use inlay_map::InlayMap;
use language::{
language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
@@ -593,13 +596,13 @@ impl DisplaySnapshot {
&self,
display_row: u32,
TextLayoutDetails {
text_system,
editor_style,
rem_size,
scroll_anchor: _,
visible_rows: _,
vertical_scroll_margin: _,
}: &TextLayoutDetails,
cx: &WindowContext,
) -> Arc<LineLayout> {
let mut runs = Vec::new();
let mut line = String::new();
@@ -628,7 +631,7 @@ impl DisplaySnapshot {
}
let font_size = editor_style.text.font_size.to_pixels(*rem_size);
text_system
cx.text_system()
.layout_line(&line, font_size, &runs)
.expect("we expect the font to be loaded because it's rendered by the editor")
}
@@ -637,8 +640,9 @@ impl DisplaySnapshot {
&self,
display_point: DisplayPoint,
text_layout_details: &TextLayoutDetails,
cx: &WindowContext,
) -> Pixels {
let line = self.layout_row(display_point.row(), text_layout_details);
let line = self.layout_row(display_point.row(), text_layout_details, cx);
line.x_for_index(display_point.column() as usize)
}
@@ -647,8 +651,9 @@ impl DisplaySnapshot {
display_row: u32,
x: Pixels,
details: &TextLayoutDetails,
cx: &WindowContext,
) -> u32 {
let layout_line = self.layout_row(display_row, details);
let layout_line = self.layout_row(display_row, details, cx);
layout_line.closest_index_for_x(x) as u32
}
@@ -1336,7 +1341,8 @@ pub mod tests {
DisplayPoint::new(0, 7)
);
let x = snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details);
let x =
snapshot.x_for_display_point(DisplayPoint::new(1, 10), &text_layout_details, cx);
assert_eq!(
movement::up(
&snapshot,

View File

@@ -51,7 +51,7 @@ pub use display_map::DisplayPoint;
use display_map::*;
pub use editor_settings::EditorSettings;
use element::LineWithInvisibles;
pub use element::{Cursor, EditorElement, HighlightedRange, HighlightedRangeLine};
pub use element::{CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine};
use futures::FutureExt;
use fuzzy::{StringMatch, StringMatchCandidate};
use git::diff_hunk_to_display;
@@ -1642,7 +1642,7 @@ impl Editor {
.update(cx, |project, cx| project.create_buffer("", None, cx))
.log_err()
{
workspace.add_item(
workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| Editor::for_buffer(buffer, Some(project.clone()), cx))),
cx,
);
@@ -3173,7 +3173,6 @@ impl Editor {
pub fn text_layout_details(&self, cx: &WindowContext) -> TextLayoutDetails {
TextLayoutDetails {
text_system: cx.text_system().clone(),
editor_style: self.style.clone().unwrap(),
rem_size: cx.rem_size(),
scroll_anchor: self.scroll_manager.anchor(),
@@ -3639,7 +3638,7 @@ impl Editor {
let project = workspace.project().clone();
let editor =
cx.new_view(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), cx));
workspace.add_item(Box::new(editor.clone()), cx);
workspace.add_item_to_active_pane(Box::new(editor.clone()), cx);
editor.update(cx, |editor, cx| {
editor.highlight_background::<Self>(
ranges_to_highlight,
@@ -4099,7 +4098,7 @@ impl Editor {
_line_height: Pixels,
_gutter_margin: Pixels,
editor_view: View<Editor>,
) -> Vec<Option<IconButton>> {
) -> Vec<Option<AnyElement>> {
fold_data
.iter()
.enumerate()
@@ -4126,6 +4125,7 @@ impl Editor {
.selected(fold_status == FoldStatus::Folded)
.selected_icon(ui::IconName::ChevronRight)
.size(ui::ButtonSize::None)
.into_any_element()
})
})
.flatten()
@@ -5258,7 +5258,7 @@ impl Editor {
head = display_map.clip_point(head, Bias::Right);
let goal = SelectionGoal::HorizontalPosition(
display_map
.x_for_display_point(head, &text_layout_details)
.x_for_display_point(head, &text_layout_details, cx)
.into(),
);
selection.collapse_to(head, goal);
@@ -6297,8 +6297,8 @@ impl Editor {
let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
let range = oldest_selection.display_range(&display_map).sorted();
let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
let start_x = display_map.x_for_display_point(range.start, &text_layout_details, cx);
let end_x = display_map.x_for_display_point(range.end, &text_layout_details, cx);
let positions = start_x.min(end_x)..start_x.max(end_x);
selections.clear();
@@ -6337,16 +6337,17 @@ impl Editor {
let range = selection.display_range(&display_map).sorted();
debug_assert_eq!(range.start.row(), range.end.row());
let mut row = range.start.row();
let positions =
if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
px(start)..px(end)
} else {
let start_x =
display_map.x_for_display_point(range.start, &text_layout_details);
let end_x =
display_map.x_for_display_point(range.end, &text_layout_details);
start_x.min(end_x)..start_x.max(end_x)
};
let positions = if let SelectionGoal::HorizontalRange { start, end } =
selection.goal
{
px(start)..px(end)
} else {
let start_x =
display_map.x_for_display_point(range.start, &text_layout_details, cx);
let end_x =
display_map.x_for_display_point(range.end, &text_layout_details, cx);
start_x.min(end_x)..start_x.max(end_x)
};
while row != end_row {
if above {
@@ -7033,7 +7034,7 @@ impl Editor {
let display_point = point.to_display_point(display_snapshot);
let goal = SelectionGoal::HorizontalPosition(
display_snapshot
.x_for_display_point(display_point, &text_layout_details)
.x_for_display_point(display_point, &text_layout_details, cx)
.into(),
);
(display_point, goal)
@@ -7506,11 +7507,13 @@ impl Editor {
cx.window_context().defer(move |cx| {
let target_editor: View<Self> =
workspace.update(cx, |workspace, cx| {
if split {
workspace.split_project_item(target.buffer.clone(), cx)
let pane = if split {
workspace.adjacent_pane(cx)
} else {
workspace.open_project_item(target.buffer.clone(), cx)
}
workspace.active_pane().clone()
};
workspace.open_project_item(pane, target.buffer.clone(), cx)
});
target_editor.update(cx, |target_editor, cx| {
// When selecting a definition in a different buffer, disable the nav history
@@ -7661,12 +7664,68 @@ impl Editor {
let workspace = self.workspace()?;
let project = workspace.read(cx).project().clone();
let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
Some(cx.spawn(|_, mut cx| async move {
let locations = references.await?;
Some(cx.spawn(|editor, mut cx| async move {
let mut locations = references.await?;
let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
let head_offset = text::ToOffset::to_offset(&head, &snapshot);
// LSP may return references that contain the item itself we requested `find_all_references` for (eg. rust-analyzer)
// So we will remove it from locations
// If there is only one reference, we will not do this filter cause it may make locations empty
if locations.len() > 1 {
cx.update(|cx| {
locations.retain(|location| {
// fn foo(x : i64) {
// ^
// println!(x);
// }
// It is ok to find reference when caret being at ^ (the end of the word)
// So we turn offset into inclusive to include the end of the word
!location
.range
.to_offset(location.buffer.read(cx))
.to_inclusive()
.contains(&head_offset)
});
})?;
}
if locations.is_empty() {
return Ok(());
}
// If there is one reference, just open it directly
if locations.len() == 1 {
let target = locations.pop().unwrap();
return editor.update(&mut cx, |editor, cx| {
let range = target.range.to_offset(target.buffer.read(cx));
let range = editor.range_for_match(&range);
if Some(&target.buffer) == editor.buffer().read(cx).as_singleton().as_ref() {
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
});
} else {
cx.window_context().defer(move |cx| {
let target_editor: View<Self> =
workspace.update(cx, |workspace, cx| {
workspace.open_project_item(
workspace.active_pane().clone(),
target.buffer.clone(),
cx,
)
});
target_editor.update(cx, |target_editor, cx| {
target_editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
s.select_ranges([range]);
})
})
})
}
});
}
workspace.update(&mut cx, |workspace, cx| {
let title = locations
.first()
@@ -7747,7 +7806,7 @@ impl Editor {
if split {
workspace.split_item(SplitDirection::Right, Box::new(editor), cx);
} else {
workspace.add_item(Box::new(editor), cx);
workspace.add_item_to_active_pane(Box::new(editor), cx);
}
}
@@ -9067,7 +9126,15 @@ impl Editor {
self.searchable
}
fn open_excerpts_in_split(&mut self, _: &OpenExcerptsSplit, cx: &mut ViewContext<Self>) {
self.open_excerpts_common(true, cx)
}
fn open_excerpts(&mut self, _: &OpenExcerpts, cx: &mut ViewContext<Self>) {
self.open_excerpts_common(false, cx)
}
fn open_excerpts_common(&mut self, split: bool, cx: &mut ViewContext<Self>) {
let buffer = self.buffer.read(cx);
if buffer.is_singleton() {
cx.propagate();
@@ -9094,18 +9161,20 @@ impl Editor {
}
}
self.push_to_nav_history(self.selections.newest_anchor().head(), None, cx);
// We defer the pane interaction because we ourselves are a workspace item
// and activating a new item causes the pane to call a method on us reentrantly,
// which panics if we're on the stack.
cx.window_context().defer(move |cx| {
workspace.update(cx, |workspace, cx| {
let pane = workspace.active_pane().clone();
let pane = if split {
workspace.adjacent_pane(cx)
} else {
workspace.active_pane().clone()
};
pane.update(cx, |pane, _| pane.disable_history());
for (buffer, ranges) in new_selections_by_buffer.into_iter() {
let editor = workspace.open_project_item::<Self>(buffer, cx);
let editor = workspace.open_project_item::<Self>(pane.clone(), buffer, cx);
editor.update(cx, |editor, cx| {
editor.change_selections(Some(Autoscroll::newest()), cx, |s| {
s.select_ranges(ranges);
@@ -10069,7 +10138,7 @@ impl ViewInputHandler for Editor {
let scroll_left = scroll_position.x * em_width;
let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
let x = snapshot.x_for_display_point(start, &text_layout_details, cx) - scroll_left
+ self.gutter_width;
let y = line_height * (start.row() as f32 - scroll_position.y);

View File

@@ -8098,6 +8098,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
"Some other server name".into(),
LspSettings {
settings: None,
initialization_options: Some(json!({
"some other init value": false
})),
@@ -8115,6 +8116,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
language_server_name.into(),
LspSettings {
settings: None,
initialization_options: Some(json!({
"anotherInitValue": false
})),
@@ -8132,6 +8134,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
language_server_name.into(),
LspSettings {
settings: None,
initialization_options: Some(json!({
"anotherInitValue": false
})),
@@ -8149,6 +8152,7 @@ async fn test_language_server_restart_due_to_settings_change(cx: &mut gpui::Test
project_settings.lsp.insert(
language_server_name.into(),
LspSettings {
settings: None,
initialization_options: None,
},
);
@@ -8426,6 +8430,105 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
);
}
#[gpui::test]
async fn test_find_all_references(cx: &mut gpui::TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
document_formatting_provider: Some(lsp::OneOf::Left(true)),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"
fn foo(«paramˇ»: i64) {
println!(param);
}
"});
cx.lsp
.handle_request::<lsp::request::References, _, _>(move |_, _| async move {
Ok(Some(vec![
lsp::Location {
uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(0, 7), lsp::Position::new(0, 12)),
},
lsp::Location {
uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 18)),
},
]))
});
let references = cx
.update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
.unwrap();
cx.executor().run_until_parked();
cx.executor().start_waiting();
references.await.unwrap();
cx.assert_editor_state(indoc! {"
fn foo(param: i64) {
println!(«paramˇ»);
}
"});
let references = cx
.update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
.unwrap();
cx.executor().run_until_parked();
cx.executor().start_waiting();
references.await.unwrap();
cx.assert_editor_state(indoc! {"
fn foo(«paramˇ»: i64) {
println!(param);
}
"});
cx.set_state(indoc! {"
fn foo(param: i64) {
let a = param;
let aˇ = param;
let a = param;
println!(param);
}
"});
cx.lsp
.handle_request::<lsp::request::References, _, _>(move |_, _| async move {
Ok(Some(vec![lsp::Location {
uri: lsp::Url::from_file_path("/root/dir/file.rs").unwrap(),
range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 9)),
}]))
});
let references = cx
.update_editor(|editor, cx| editor.find_all_references(&FindAllReferences, cx))
.unwrap();
cx.executor().run_until_parked();
cx.executor().start_waiting();
references.await.unwrap();
cx.assert_editor_state(indoc! {"
fn foo(param: i64) {
let a = param;
let «aˇ» = param;
let a = param;
println!(param);
}
"});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(row as u32, column as u32);
point..point

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,9 @@ enum GitHostingProvider {
Github,
Gitlab,
Gitee,
Bitbucket,
Sourcehut,
Codeberg,
}
impl GitHostingProvider {
@@ -16,6 +19,9 @@ impl GitHostingProvider {
Self::Github => "https://github.com",
Self::Gitlab => "https://gitlab.com",
Self::Gitee => "https://gitee.com",
Self::Bitbucket => "https://bitbucket.org",
Self::Sourcehut => "https://git.sr.ht",
Self::Codeberg => "https://codeberg.org",
};
Url::parse(&base_url).unwrap()
@@ -28,16 +34,21 @@ impl GitHostingProvider {
let line = selection.start.row + 1;
match self {
Self::Github | Self::Gitlab | Self::Gitee => format!("L{}", line),
Self::Github | Self::Gitlab | Self::Gitee | Self::Sourcehut | Self::Codeberg => {
format!("L{}", line)
}
Self::Bitbucket => format!("lines-{}", line),
}
} else {
let start_line = selection.start.row + 1;
let end_line = selection.end.row + 1;
match self {
Self::Github => format!("L{}-L{}", start_line, end_line),
Self::Gitlab => format!("L{}-{}", start_line, end_line),
Self::Gitee => format!("L{}-{}", start_line, end_line),
Self::Github | Self::Codeberg => format!("L{}-L{}", start_line, end_line),
Self::Gitlab | Self::Gitee | Self::Sourcehut => {
format!("L{}-{}", start_line, end_line)
}
Self::Bitbucket => format!("lines-{}:{}", start_line, end_line),
}
}
}
@@ -69,6 +80,9 @@ pub fn build_permalink(params: BuildPermalinkParams) -> Result<Url> {
GitHostingProvider::Github => format!("{owner}/{repo}/blob/{sha}/{path}"),
GitHostingProvider::Gitlab => format!("{owner}/{repo}/-/blob/{sha}/{path}"),
GitHostingProvider::Gitee => format!("{owner}/{repo}/blob/{sha}/{path}"),
GitHostingProvider::Bitbucket => format!("{owner}/{repo}/src/{sha}/{path}"),
GitHostingProvider::Sourcehut => format!("~{owner}/{repo}/tree/{sha}/item/{path}"),
GitHostingProvider::Codeberg => format!("{owner}/{repo}/src/commit/{sha}/{path}"),
};
let line_fragment = selection.map(|selection| provider.line_fragment(&selection));
@@ -130,6 +144,52 @@ fn parse_git_remote_url(url: &str) -> Option<ParsedGitRemote> {
});
}
if url.contains("bitbucket.org") {
let (_, repo_with_owner) = url.trim_end_matches(".git").split_once("bitbucket.org")?;
let (owner, repo) = repo_with_owner
.trim_start_matches("/")
.trim_start_matches(":")
.split_once("/")?;
return Some(ParsedGitRemote {
provider: GitHostingProvider::Bitbucket,
owner,
repo,
});
}
if url.starts_with("git@git.sr.ht:") || url.starts_with("https://git.sr.ht/") {
// sourcehut indicates a repo with '.git' suffix as a separate repo.
// For example, "git@git.sr.ht:~username/repo" and "git@git.sr.ht:~username/repo.git"
// are two distinct repositories.
let repo_with_owner = url
.trim_start_matches("git@git.sr.ht:~")
.trim_start_matches("https://git.sr.ht/~");
let (owner, repo) = repo_with_owner.split_once("/")?;
return Some(ParsedGitRemote {
provider: GitHostingProvider::Sourcehut,
owner,
repo,
});
}
if url.starts_with("git@codeberg.org:") || url.starts_with("https://codeberg.org/") {
let repo_with_owner = url
.trim_start_matches("git@codeberg.org:")
.trim_start_matches("https://codeberg.org/")
.trim_end_matches(".git");
let (owner, repo) = repo_with_owner.split_once("/")?;
return Some(ParsedGitRemote {
provider: GitHostingProvider::Codeberg,
owner,
repo,
});
}
None
}
@@ -387,4 +447,257 @@ mod tests {
let expected_url = "https://gitee.com/libkitten/zed/blob/e5fe811d7ad0fc26934edd76f891d20bdc3bb194/crates/zed/src/main.rs#L24-48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_parse_git_remote_url_bitbucket_https_with_username() {
let url = "https://thorstenballzed@bitbucket.org/thorstenzed/testingrepo.git";
let parsed = parse_git_remote_url(url).unwrap();
assert!(matches!(parsed.provider, GitHostingProvider::Bitbucket));
assert_eq!(parsed.owner, "thorstenzed");
assert_eq!(parsed.repo, "testingrepo");
}
#[test]
fn test_parse_git_remote_url_bitbucket_https_without_username() {
let url = "https://bitbucket.org/thorstenzed/testingrepo.git";
let parsed = parse_git_remote_url(url).unwrap();
assert!(matches!(parsed.provider, GitHostingProvider::Bitbucket));
assert_eq!(parsed.owner, "thorstenzed");
assert_eq!(parsed.repo, "testingrepo");
}
#[test]
fn test_parse_git_remote_url_bitbucket_git() {
let url = "git@bitbucket.org:thorstenzed/testingrepo.git";
let parsed = parse_git_remote_url(url).unwrap();
assert!(matches!(parsed.provider, GitHostingProvider::Bitbucket));
assert_eq!(parsed.owner, "thorstenzed");
assert_eq!(parsed.repo, "testingrepo");
}
#[test]
fn test_build_bitbucket_permalink_from_ssh_url() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@bitbucket.org:thorstenzed/testingrepo.git",
sha: "f00b4r",
path: "main.rs",
selection: None,
})
.unwrap();
let expected_url = "https://bitbucket.org/thorstenzed/testingrepo/src/f00b4r/main.rs";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_bitbucket_permalink_from_ssh_url_single_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@bitbucket.org:thorstenzed/testingrepo.git",
sha: "f00b4r",
path: "main.rs",
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
})
.unwrap();
let expected_url =
"https://bitbucket.org/thorstenzed/testingrepo/src/f00b4r/main.rs#lines-7";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_bitbucket_permalink_from_ssh_url_multi_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@bitbucket.org:thorstenzed/testingrepo.git",
sha: "f00b4r",
path: "main.rs",
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
})
.unwrap();
let expected_url =
"https://bitbucket.org/thorstenzed/testingrepo/src/f00b4r/main.rs#lines-24:48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_ssh_url() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@git.sr.ht:~rajveermalviya/zed",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: None,
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_ssh_url_with_git_prefix() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@git.sr.ht:~rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: None,
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed.git/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_ssh_url_single_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@git.sr.ht:~rajveermalviya/zed",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L7";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_ssh_url_multi_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@git.sr.ht:~rajveermalviya/zed",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/editor/src/git/permalink.rs#L24-48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_https_url() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "https://git.sr.ht/~rajveermalviya/zed",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/zed/src/main.rs",
selection: None,
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/zed/src/main.rs";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_https_url_single_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "https://git.sr.ht/~rajveermalviya/zed",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/zed/src/main.rs",
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/zed/src/main.rs#L7";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_sourcehut_permalink_from_https_url_multi_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "https://git.sr.ht/~rajveermalviya/zed",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/zed/src/main.rs",
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
})
.unwrap();
let expected_url = "https://git.sr.ht/~rajveermalviya/zed/tree/faa6f979be417239b2e070dbbf6392b909224e0b/item/crates/zed/src/main.rs#L24-48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_codeberg_permalink_from_ssh_url() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@codeberg.org:rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: None,
})
.unwrap();
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/editor/src/git/permalink.rs";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_codeberg_permalink_from_ssh_url_single_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@codeberg.org:rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
})
.unwrap();
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/editor/src/git/permalink.rs#L7";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_codeberg_permalink_from_ssh_url_multi_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "git@codeberg.org:rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/editor/src/git/permalink.rs",
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
})
.unwrap();
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/editor/src/git/permalink.rs#L24-L48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_codeberg_permalink_from_https_url() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "https://codeberg.org/rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/zed/src/main.rs",
selection: None,
})
.unwrap();
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/zed/src/main.rs";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_codeberg_permalink_from_https_url_single_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "https://codeberg.org/rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/zed/src/main.rs",
selection: Some(Point::new(6, 1)..Point::new(6, 10)),
})
.unwrap();
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/zed/src/main.rs#L7";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
#[test]
fn test_build_codeberg_permalink_from_https_url_multi_line_selection() {
let permalink = build_permalink(BuildPermalinkParams {
remote_url: "https://codeberg.org/rajveermalviya/zed.git",
sha: "faa6f979be417239b2e070dbbf6392b909224e0b",
path: "crates/zed/src/main.rs",
selection: Some(Point::new(23, 1)..Point::new(47, 10)),
})
.unwrap();
let expected_url = "https://codeberg.org/rajveermalviya/zed/src/commit/faa6f979be417239b2e070dbbf6392b909224e0b/crates/zed/src/main.rs#L24-L48";
assert_eq!(permalink.to_string(), expected_url.to_string())
}
}

View File

@@ -117,7 +117,7 @@ pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut Vie
// Highlight the selected symbol using a background highlight
this.highlight_inlay_background::<HoverState>(
vec![inlay_hover.range],
|theme| theme.element_hover, // todo!("use a proper background here")
|theme| theme.element_hover, // todo("use a proper background here")
cx,
);
this.hover_state.info_popover = Some(hover_popover);
@@ -332,7 +332,7 @@ fn show_hover(
// Highlight the selected symbol using a background highlight
this.highlight_background::<HoverState>(
vec![symbol_range],
|theme| theme.element_hover, // todo! update theme
|theme| theme.element_hover, // todo update theme
cx,
);
} else {

View File

@@ -3,11 +3,11 @@
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
use crate::{char_kind, scroll::ScrollAnchor, CharKind, EditorStyle, ToOffset, ToPoint};
use gpui::{px, Pixels, WindowTextSystem};
use gpui::{px, Pixels};
use language::Point;
use multi_buffer::MultiBufferSnapshot;
use std::{ops::Range, sync::Arc};
use std::ops::Range;
/// Defines search strategy for items in `movement` module.
/// `FindRange::SingeLine` only looks for a match on a single line at a time, whereas
@@ -21,7 +21,6 @@ pub enum FindRange {
/// TextLayoutDetails encompasses everything we need to move vertically
/// taking into account variable width characters.
pub struct TextLayoutDetails {
pub(crate) text_system: Arc<WindowTextSystem>,
pub(crate) editor_style: EditorStyle,
pub(crate) rem_size: Pixels,
pub scroll_anchor: ScrollAnchor,

View File

@@ -105,7 +105,7 @@ pub fn expand_macro_recursively(
let buffer = cx.new_model(|cx| {
MultiBuffer::singleton(buffer, cx).with_title(macro_expansion.name)
});
workspace.add_item(
workspace.add_item_to_active_pane(
Box::new(cx.new_view(|cx| Editor::for_multibuffer(buffer, Some(project), cx))),
cx,
);

View File

@@ -24,7 +24,7 @@ pub fn init(cx: &mut AppContext) {
cx.observe_new_views(move |workspace: &mut Workspace, _cx| {
workspace.register_action(move |workspace, _: &Extensions, cx| {
let extensions_page = ExtensionsPage::new(workspace, cx);
workspace.add_item(Box::new(extensions_page), cx)
workspace.add_item_to_active_pane(Box::new(extensions_page), cx)
});
})
.detach();
@@ -55,7 +55,11 @@ impl ExtensionsPage {
let store = ExtensionStore::global(cx);
let subscription = cx.observe(&store, |_, _, cx| cx.notify());
let query_editor = cx.new_view(|cx| Editor::single_line(cx));
let query_editor = cx.new_view(|cx| {
let mut input = Editor::single_line(cx);
input.set_placeholder_text("Search extensions...", cx);
input
});
cx.subscribe(&query_editor, Self::on_query_change).detach();
let mut this = Self {
@@ -464,88 +468,96 @@ impl Render for ExtensionsPage {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.size_full()
.p_4()
.gap_4()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.w_full()
.child(Headline::new("Extensions").size(HeadlineSize::XLarge)),
)
.child(
h_flex()
.w_full()
.gap_2()
.child(h_flex().child(self.render_search(cx)))
v_flex()
.gap_4()
.p_4()
.border_b()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
.w_full()
.child(Headline::new("Extensions").size(HeadlineSize::XLarge)),
)
.child(
h_flex()
.w_full()
.gap_2()
.justify_between()
.child(h_flex().child(self.render_search(cx)))
.child(
ToggleButton::new("filter-all", "All")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(self.filter == ExtensionFilter::All)
.on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::All;
}))
.tooltip(move |cx| Tooltip::text("Show all extensions", cx))
.first(),
)
.child(
ToggleButton::new("filter-installed", "Installed")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(self.filter == ExtensionFilter::Installed)
.on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::Installed;
}))
.tooltip(move |cx| {
Tooltip::text("Show installed extensions", cx)
})
.middle(),
)
.child(
ToggleButton::new("filter-not-installed", "Not Installed")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(self.filter == ExtensionFilter::NotInstalled)
.on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::NotInstalled;
}))
.tooltip(move |cx| {
Tooltip::text("Show not installed extensions", cx)
})
.last(),
h_flex()
.child(
ToggleButton::new("filter-all", "All")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(self.filter == ExtensionFilter::All)
.on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::All;
}))
.tooltip(move |cx| {
Tooltip::text("Show all extensions", cx)
})
.first(),
)
.child(
ToggleButton::new("filter-installed", "Installed")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(self.filter == ExtensionFilter::Installed)
.on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::Installed;
}))
.tooltip(move |cx| {
Tooltip::text("Show installed extensions", cx)
})
.middle(),
)
.child(
ToggleButton::new("filter-not-installed", "Not Installed")
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.selected(self.filter == ExtensionFilter::NotInstalled)
.on_click(cx.listener(|this, _event, _cx| {
this.filter = ExtensionFilter::NotInstalled;
}))
.tooltip(move |cx| {
Tooltip::text("Show not installed extensions", cx)
})
.last(),
),
),
),
)
.child(v_flex().size_full().overflow_y_hidden().map(|this| {
.child(v_flex().px_4().size_full().overflow_y_hidden().map(|this| {
let entries = self.filtered_extension_entries(cx);
if entries.is_empty() {
return this.child(self.render_empty_state(cx));
return this.py_4().child(self.render_empty_state(cx));
}
let view = cx.view().clone();
let scroll_handle = self.list.clone();
let item_count = entries.len();
this.child(
canvas({
let view = cx.view().clone();
let scroll_handle = self.list.clone();
let item_count = entries.len();
canvas(
move |bounds, cx| {
uniform_list::<_, Div, _>(
let mut list = uniform_list::<_, Div, _>(
view,
"entries",
item_count,
Self::render_extensions,
)
.size_full()
.pb_4()
.track_scroll(scroll_handle)
.into_any_element()
.draw(
bounds.origin,
bounds.size.map(AvailableSpace::Definite),
cx,
)
}
})
.into_any_element();
list.layout(bounds.origin, bounds.size.into(), cx);
list
},
|_bounds, mut list, cx| list.paint(cx),
)
.size_full(),
)
}))

View File

@@ -365,14 +365,7 @@ impl FileFinderDelegate {
history_items: Vec<FoundPath>,
cx: &mut ViewContext<FileFinder>,
) -> Self {
cx.observe(&project, |file_finder, _, cx| {
//todo We should probably not re-render on every project anything
file_finder
.picker
.update(cx, |picker, cx| picker.refresh(cx))
})
.detach();
Self::subscribe_to_updates(&project, cx);
Self {
file_finder,
workspace,
@@ -389,6 +382,20 @@ impl FileFinderDelegate {
}
}
fn subscribe_to_updates(project: &Model<Project>, cx: &mut ViewContext<FileFinder>) {
cx.subscribe(project, |file_finder, _, event, cx| {
match event {
project::Event::WorktreeUpdatedEntries(_, _)
| project::Event::WorktreeAdded
| project::Event::WorktreeRemoved(_) => file_finder
.picker
.update(cx, |picker, cx| picker.refresh(cx)),
_ => {}
};
})
.detach();
}
fn spawn_search(
&mut self,
query: PathLikeWithPosition<FileSearchQuery>,

View File

@@ -1,9 +1,10 @@
use std::{assert_eq, path::Path, time::Duration};
use std::{assert_eq, future::IntoFuture, path::Path, time::Duration};
use super::*;
use editor::Editor;
use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext};
use project::worktree::FS_WATCH_LATENCY;
use serde_json::json;
use workspace::{AppState, Workspace};
@@ -1337,6 +1338,137 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
});
}
#[gpui::test]
async fn test_search_results_refreshed_on_worktree_updates(cx: &mut gpui::TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/src",
json!({
"lib.rs": "// Lib file",
"main.rs": "// Bar file",
"read.me": "// Readme file",
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
// Initial state
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("rs");
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "lib.rs");
assert_match_at_position(finder, 1, "main.rs");
});
// Delete main.rs
app_state
.fs
.remove_file("/src/main.rs".as_ref(), Default::default())
.await
.expect("unable to remove file");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// main.rs is in not among search results anymore
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 1);
assert_match_at_position(finder, 0, "lib.rs");
});
// Create util.rs
app_state
.fs
.create_file("/src/util.rs".as_ref(), Default::default())
.await
.expect("unable to create file");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// util.rs is among search results
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "lib.rs");
assert_match_at_position(finder, 1, "util.rs");
});
}
#[gpui::test]
async fn test_search_results_refreshed_on_adding_and_removing_worktrees(
cx: &mut gpui::TestAppContext,
) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/test",
json!({
"project_1": {
"bar.rs": "// Bar file",
"lib.rs": "// Lib file",
},
"project_2": {
"Cargo.toml": "// Cargo file",
"main.rs": "// Main file",
}
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/test/project_1".as_ref()], cx).await;
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project.clone(), cx));
let worktree_1_id = project.update(cx, |project, cx| {
let worktree = project.worktrees().last().expect("worktree not found");
worktree.read(cx).id()
});
// Initial state
let picker = open_file_picker(&workspace, cx);
cx.simulate_input("rs");
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 2);
assert_match_at_position(finder, 0, "bar.rs");
assert_match_at_position(finder, 1, "lib.rs");
});
// Add new worktree
project
.update(cx, |project, cx| {
project
.find_or_create_local_worktree("/test/project_2", true, cx)
.into_future()
})
.await
.expect("unable to create workdir");
cx.executor().advance_clock(FS_WATCH_LATENCY);
// main.rs is among search results
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 3);
assert_match_at_position(finder, 0, "bar.rs");
assert_match_at_position(finder, 1, "lib.rs");
assert_match_at_position(finder, 2, "main.rs");
});
// Remove the first worktree
project.update(cx, |project, cx| {
project.remove_worktree(worktree_1_id, cx);
});
cx.executor().advance_clock(FS_WATCH_LATENCY);
// Files from the first worktree are not in the search results anymore
picker.update(cx, |finder, _| {
assert_eq!(finder.delegate.matches.len(), 1);
assert_match_at_position(finder, 0, "main.rs");
});
}
async fn open_close_queried_buffer(
input: &str,
expected_matches: usize,

View File

@@ -1343,7 +1343,7 @@ pub fn copy_recursive<'a>(
.boxed()
}
// todo!(windows)
// todo(windows)
// can we get file id not open the file twice?
// https://github.com/rust-lang/rust/issues/63010
#[cfg(target_os = "windows")]

View File

@@ -96,13 +96,15 @@ objc = "0.2"
flume = "0.11"
open = "5.0.1"
ashpd = "0.7.0"
# todo!(linux) - Technically do not use `randr`, but it doesn't compile otherwise
# todo(linux) - Technically do not use `randr`, but it doesn't compile otherwise
xcb = { version = "1.3", features = ["as-raw-xcb-connection", "present", "randr", "xkb"] }
wayland-client= { version = "0.31.2" }
wayland-protocols = { version = "0.31.2", features = ["client", "staging", "unstable"] }
wayland-backend = { version = "0.3.3", features = ["client_system"] }
xkbcommon = { version = "0.7", features = ["wayland", "x11"] }
as-raw-xcb-connection = "1"
calloop = "0.12.4"
calloop-wayland-source = "0.2.0"
#TODO: use these on all platforms
blade-graphics.workspace = true
blade-macros.workspace = true

View File

@@ -1,9 +1,9 @@
use crate::{
Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext,
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Entity, EventEmitter,
ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext, Modifiers,
ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
AvailableSpace, BackgroundExecutor, Bounds, ClipboardItem, Context, Empty, Entity,
EventEmitter, ForegroundExecutor, Global, InputEvent, Keystroke, Model, ModelContext,
Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Platform, Point, Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow,
TextSystem, View, ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions,
};
use anyhow::{anyhow, bail};
@@ -177,7 +177,7 @@ impl TestAppContext {
/// Adds a new window with no content.
pub fn add_empty_window(&mut self) -> &mut VisualTestContext {
let mut cx = self.app.borrow_mut();
let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ()));
let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| Empty));
drop(cx);
let cx = VisualTestContext::from_window(*window.deref(), self).as_mut();
cx.run_until_parked();
@@ -674,17 +674,10 @@ impl<'a> VisualTestContext {
f: impl FnOnce(&mut WindowContext) -> AnyElement,
) {
self.update(|cx| {
let entity_id = cx
.window
.root_view
.as_ref()
.expect("Can't draw to this window without a root view")
.entity_id();
cx.with_element_context(|cx| {
cx.with_view_id(entity_id, |cx| {
f(cx).draw(origin, space, cx);
})
let mut element = f(cx);
element.layout(origin, space, cx);
element.paint(cx);
});
cx.refresh();

View File

@@ -0,0 +1,365 @@
#![allow(unused)]
use crate::{Bounds, Half, Point};
use std::{
cmp,
fmt::Debug,
ops::{Add, Sub},
};
#[derive(Debug)]
pub(crate) struct BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
root: Option<usize>,
nodes: Vec<Node<U, T>>,
stack: Vec<usize>,
}
impl<U, T> BoundsTree<U, T>
where
U: Clone + Debug + PartialOrd + Add<U, Output = U> + Sub<Output = U> + Half + Default,
T: Clone + Debug,
{
pub fn clear(&mut self) {
self.root = None;
self.nodes.clear();
self.stack.clear();
}
pub fn insert(&mut self, new_bounds: Bounds<U>, payload: T) -> u32 {
// If the tree is empty, make the root the new leaf.
if self.root.is_none() {
let new_node = self.push_leaf(new_bounds, payload, 1);
self.root = Some(new_node);
return 1;
}
// Search for the best place to add the new leaf based on heuristics.
let mut max_intersecting_ordering = 0;
let mut index = self.root.unwrap();
while let Node::Internal {
left,
right,
bounds: node_bounds,
..
} = &mut self.nodes[index]
{
let left = *left;
let right = *right;
*node_bounds = node_bounds.union(&new_bounds);
self.stack.push(index);
// Descend to the best-fit child, based on which one would increase
// the surface area the least. This attempts to keep the tree balanced
// in terms of surface area. If there is an intersection with the other child,
// add its keys to the intersections vector.
let left_cost = new_bounds
.union(&self.nodes[left].bounds())
.half_perimeter();
let right_cost = new_bounds
.union(&self.nodes[right].bounds())
.half_perimeter();
if left_cost < right_cost {
max_intersecting_ordering =
self.find_max_ordering(right, &new_bounds, max_intersecting_ordering);
index = left;
} else {
max_intersecting_ordering =
self.find_max_ordering(left, &new_bounds, max_intersecting_ordering);
index = right;
}
}
// We've found a leaf ('index' now refers to a leaf node).
// We'll insert a new parent node above the leaf and attach our new leaf to it.
let sibling = index;
// Check for collision with the located leaf node
let Node::Leaf {
bounds: sibling_bounds,
order: sibling_ordering,
..
} = &self.nodes[index]
else {
unreachable!();
};
if sibling_bounds.intersects(&new_bounds) {
max_intersecting_ordering = cmp::max(max_intersecting_ordering, *sibling_ordering);
}
let ordering = max_intersecting_ordering + 1;
let new_node = self.push_leaf(new_bounds, payload, ordering);
let new_parent = self.push_internal(sibling, new_node);
// If there was an old parent, we need to update its children indices.
if let Some(old_parent) = self.stack.last().copied() {
let Node::Internal { left, right, .. } = &mut self.nodes[old_parent] else {
unreachable!();
};
if *left == sibling {
*left = new_parent;
} else {
*right = new_parent;
}
} else {
// If the old parent was the root, the new parent is the new root.
self.root = Some(new_parent);
}
for node_index in self.stack.drain(..) {
let Node::Internal {
max_order: max_ordering,
..
} = &mut self.nodes[node_index]
else {
unreachable!()
};
*max_ordering = cmp::max(*max_ordering, ordering);
}
ordering
}
/// Finds all nodes whose bounds contain the given point and pushes their (bounds, payload) pairs onto the result vector.
pub(crate) fn find_containing(
&mut self,
point: &Point<U>,
result: &mut Vec<BoundsSearchResult<U, T>>,
) {
if let Some(mut index) = self.root {
self.stack.clear();
self.stack.push(index);
while let Some(current_index) = self.stack.pop() {
match &self.nodes[current_index] {
Node::Leaf {
bounds,
order,
data,
} => {
if bounds.contains(point) {
result.push(BoundsSearchResult {
bounds: bounds.clone(),
order: *order,
data: data.clone(),
});
}
}
Node::Internal {
left,
right,
bounds,
..
} => {
if bounds.contains(point) {
self.stack.push(*left);
self.stack.push(*right);
}
}
}
}
}
}
fn find_max_ordering(&self, index: usize, bounds: &Bounds<U>, mut max_ordering: u32) -> u32 {
match {
let this = &self;
&this.nodes[index]
} {
Node::Leaf {
bounds: node_bounds,
order: ordering,
..
} => {
if bounds.intersects(node_bounds) {
max_ordering = cmp::max(*ordering, max_ordering);
}
}
Node::Internal {
left,
right,
bounds: node_bounds,
max_order: node_max_ordering,
..
} => {
if bounds.intersects(node_bounds) && max_ordering < *node_max_ordering {
let left_max_ordering = self.nodes[*left].max_ordering();
let right_max_ordering = self.nodes[*right].max_ordering();
if left_max_ordering > right_max_ordering {
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
} else {
max_ordering = self.find_max_ordering(*right, bounds, max_ordering);
max_ordering = self.find_max_ordering(*left, bounds, max_ordering);
}
}
}
}
max_ordering
}
fn push_leaf(&mut self, bounds: Bounds<U>, payload: T, order: u32) -> usize {
self.nodes.push(Node::Leaf {
bounds,
data: payload,
order,
});
self.nodes.len() - 1
}
fn push_internal(&mut self, left: usize, right: usize) -> usize {
let left_node = &self.nodes[left];
let right_node = &self.nodes[right];
let new_bounds = left_node.bounds().union(right_node.bounds());
let max_ordering = cmp::max(left_node.max_ordering(), right_node.max_ordering());
self.nodes.push(Node::Internal {
bounds: new_bounds,
left,
right,
max_order: max_ordering,
});
self.nodes.len() - 1
}
}
impl<U, T> Default for BoundsTree<U, T>
where
U: Default + Clone + Debug,
T: Clone + Debug,
{
fn default() -> Self {
BoundsTree {
root: None,
nodes: Vec::new(),
stack: Vec::new(),
}
}
}
#[derive(Debug, Clone)]
enum Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + Debug,
{
Leaf {
bounds: Bounds<U>,
order: u32,
data: T,
},
Internal {
left: usize,
right: usize,
bounds: Bounds<U>,
max_order: u32,
},
}
impl<U, T> Node<U, T>
where
U: Clone + Default + Debug,
T: Clone + Debug,
{
fn bounds(&self) -> &Bounds<U> {
match self {
Node::Leaf { bounds, .. } => bounds,
Node::Internal { bounds, .. } => bounds,
}
}
fn max_ordering(&self) -> u32 {
match self {
Node::Leaf {
order: ordering, ..
} => *ordering,
Node::Internal {
max_order: max_ordering,
..
} => *max_ordering,
}
}
}
pub(crate) struct BoundsSearchResult<U: Clone + Default + Debug, T> {
pub bounds: Bounds<U>,
pub order: u32,
pub data: T,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Bounds, Point, Size};
#[test]
fn test_insert_and_find_containing() {
let mut tree = BoundsTree::<f32, String>::default();
let bounds1 = Bounds {
origin: Point { x: 0.0, y: 0.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
let bounds2 = Bounds {
origin: Point { x: 5.0, y: 5.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
let bounds3 = Bounds {
origin: Point { x: 10.0, y: 10.0 },
size: Size {
width: 10.0,
height: 10.0,
},
};
// Insert bounds into the tree
tree.insert(bounds1.clone(), "Payload 1".to_string());
tree.insert(bounds2.clone(), "Payload 2".to_string());
tree.insert(bounds3.clone(), "Payload 3".to_string());
// Points for testing
let point_inside_bounds1 = Point { x: 1.0, y: 1.0 };
let point_inside_bounds1_and_2 = Point { x: 6.0, y: 6.0 };
let point_inside_bounds2_and_3 = Point { x: 12.0, y: 12.0 };
let point_outside_all_bounds = Point { x: 21.0, y: 21.0 };
assert!(!bounds1.contains(&point_inside_bounds2_and_3));
assert!(!bounds1.contains(&point_outside_all_bounds));
assert!(bounds2.contains(&point_inside_bounds1_and_2));
assert!(bounds2.contains(&point_inside_bounds2_and_3));
assert!(!bounds2.contains(&point_outside_all_bounds));
assert!(!bounds3.contains(&point_inside_bounds1));
assert!(bounds3.contains(&point_inside_bounds2_and_3));
assert!(!bounds3.contains(&point_outside_all_bounds));
// Test find_containing for different points
let mut result = Vec::new();
tree.find_containing(&point_inside_bounds1, &mut result);
assert_eq!(result.len(), 1);
assert_eq!(result[0].data, "Payload 1");
result.clear();
tree.find_containing(&point_inside_bounds1_and_2, &mut result);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|r| r.data == "Payload 1"));
assert!(result.iter().any(|r| r.data == "Payload 2"));
result.clear();
tree.find_containing(&point_inside_bounds2_and_3, &mut result);
assert_eq!(result.len(), 2);
assert!(result.iter().any(|r| r.data == "Payload 2"));
assert!(result.iter().any(|r| r.data == "Payload 3"));
result.clear();
tree.find_containing(&point_outside_all_bounds, &mut result);
assert_eq!(result.len(), 0);
}
}

View File

@@ -15,9 +15,6 @@
//!
//! But some state is too simple and voluminous to store in every view that needs it, e.g.
//! whether a hover has been started or not. For this, GPUI provides the [`Element::State`], associated type.
//! If an element returns an [`ElementId`] from [`IntoElement::element_id()`], and that element id
//! appears in the same place relative to other views and ElementIds in the frame, then the previous
//! frame's state will be passed to the element's layout and paint methods.
//!
//! # Implementing your own elements
//!
@@ -35,33 +32,48 @@
//! your own custom layout algorithm or rendering a code editor.
use crate::{
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, ElementContext, ElementId, LayoutId,
Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
util::FluentBuilder, ArenaBox, AvailableSpace, Bounds, DispatchNodeId, ElementContext,
ElementId, LayoutId, Pixels, Point, Size, ViewContext, WindowContext, ELEMENT_ARENA,
};
use derive_more::{Deref, DerefMut};
pub(crate) use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, ops::DerefMut};
use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
/// Implemented by types that participate in laying out and painting the contents of a window.
/// Elements form a tree and are laid out according to web-based layout rules, as implemented by Taffy.
/// You can create custom elements by implementing this trait, see the module-level documentation
/// for more details.
pub trait Element: 'static + IntoElement {
/// The type of state to store for this element between frames. See the module-level documentation
/// for details.
type State: 'static;
/// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently
/// provided to [`Element::after_layout`] and [`Element::paint`].
type BeforeLayout: 'static;
/// The type of state returned from [`Element::after_layout`]. A mutable reference to this state is subsequently
/// provided to [`Element::paint`].
type AfterLayout: 'static;
/// Before an element can be painted, we need to know where it's going to be and how big it is.
/// Use this method to request a layout from Taffy and initialize the element's state.
fn request_layout(
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout);
/// After laying out an element, we need to commit its bounds to the current frame for hitbox
/// purposes. The state argument is the same state that was returned from [`Element::before_layout()`].
fn after_layout(
&mut self,
state: Option<Self::State>,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (LayoutId, Self::State);
) -> Self::AfterLayout;
/// Once layout has been completed, this method will be called to paint the element to the screen.
/// The state argument is the same state that was returned from [`Element::request_layout()`].
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext);
/// The state argument is the same state that was returned from [`Element::before_layout()`].
fn paint(
&mut self,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
);
/// Convert this element into a dynamically-typed [`AnyElement`].
fn into_any(self) -> AnyElement {
@@ -75,10 +87,6 @@ pub trait IntoElement: Sized {
/// Useful for converting other types into elements automatically, like Strings
type Element: Element;
/// The [`ElementId`] of self once converted into an [`Element`].
/// If present, the resulting element's state will be carried across frames.
fn element_id(&self) -> Option<ElementId>;
/// Convert self into a type that implements [`Element`].
fn into_element(self) -> Self::Element;
@@ -86,41 +94,6 @@ pub trait IntoElement: Sized {
fn into_any_element(self) -> AnyElement {
self.into_element().into_any()
}
/// Convert into an element, then draw in the current window at the given origin.
/// The available space argument is provided to the layout engine to determine the size of the
// root element. Once the element is drawn, its associated element state is yielded to the
// given callback.
fn draw_and_update_state<T, R>(
self,
origin: Point<Pixels>,
available_space: Size<T>,
cx: &mut ElementContext,
f: impl FnOnce(&mut <Self::Element as Element>::State, &mut ElementContext) -> R,
) -> R
where
T: Clone + Default + Debug + Into<AvailableSpace>,
{
let element = self.into_element();
let element_id = element.element_id();
let element = DrawableElement {
element: Some(element),
phase: ElementDrawPhase::Start,
};
let frame_state =
DrawableElement::draw(element, origin, available_space.map(Into::into), cx);
if let Some(mut frame_state) = frame_state {
f(&mut frame_state, cx)
} else {
cx.with_element_state(element_id.unwrap(), |element_state, cx| {
let mut element_state = element_state.unwrap();
let result = f(&mut element_state, cx);
(result, element_state)
})
}
}
}
impl<T: IntoElement> FluentBuilder for T {}
@@ -132,8 +105,10 @@ pub trait Render: 'static + Sized {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement;
}
impl Render for () {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {}
impl Render for Empty {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
Empty
}
}
/// You can derive [`IntoElement`] on any type that implements this trait.
@@ -186,24 +161,36 @@ impl<C: RenderOnce> Component<C> {
}
impl<C: RenderOnce> Element for Component<C> {
type State = AnyElement;
type BeforeLayout = AnyElement;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut element = self
.0
.take()
.unwrap()
.render(cx.deref_mut())
.into_any_element();
let layout_id = element.request_layout(cx);
let layout_id = element.before_layout(cx);
(layout_id, element)
}
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
fn after_layout(
&mut self,
_: Bounds<Pixels>,
element: &mut AnyElement,
cx: &mut ElementContext,
) {
element.after_layout(cx);
}
fn paint(
&mut self,
_: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
element.paint(cx)
}
}
@@ -211,10 +198,6 @@ impl<C: RenderOnce> Element for Component<C> {
impl<C: RenderOnce> IntoElement for Component<C> {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@@ -225,9 +208,11 @@ impl<C: RenderOnce> IntoElement for Component<C> {
pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
trait ElementObject {
fn element_id(&self) -> Option<ElementId>;
fn inner_element(&mut self) -> &mut dyn Any;
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
fn after_layout(&mut self, cx: &mut ElementContext);
fn paint(&mut self, cx: &mut ElementContext);
@@ -236,110 +221,103 @@ trait ElementObject {
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> Size<Pixels>;
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
);
}
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
pub(crate) struct DrawableElement<E: Element> {
element: Option<E>,
phase: ElementDrawPhase<E::State>,
pub struct Drawable<E: Element> {
/// The drawn element.
pub element: E,
phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout>,
}
#[derive(Default)]
enum ElementDrawPhase<S> {
enum ElementDrawPhase<BeforeLayout, AfterLayout> {
#[default]
Start,
LayoutRequested {
BeforeLayout {
layout_id: LayoutId,
frame_state: Option<S>,
before_layout: BeforeLayout,
},
LayoutComputed {
layout_id: LayoutId,
available_space: Size<AvailableSpace>,
frame_state: Option<S>,
before_layout: BeforeLayout,
},
AfterLayout {
node_id: DispatchNodeId,
bounds: Bounds<Pixels>,
before_layout: BeforeLayout,
after_layout: AfterLayout,
},
Painted,
}
/// A wrapper around an implementer of [`Element`] that allows it to be drawn in a window.
impl<E: Element> DrawableElement<E> {
impl<E: Element> Drawable<E> {
fn new(element: E) -> Self {
DrawableElement {
element: Some(element),
Drawable {
element,
phase: ElementDrawPhase::Start,
}
}
fn element_id(&self) -> Option<ElementId> {
self.element.as_ref()?.element_id()
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
match mem::take(&mut self.phase) {
ElementDrawPhase::Start => {
let (layout_id, before_layout) = self.element.before_layout(cx);
self.phase = ElementDrawPhase::BeforeLayout {
layout_id,
before_layout,
};
layout_id
}
_ => panic!("must call before_layout only once"),
}
}
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
let (layout_id, frame_state) = if let Some(id) = self.element.as_ref().unwrap().element_id()
{
let layout_id = cx.with_element_state(id, |element_state, cx| {
self.element
.as_mut()
.unwrap()
.request_layout(element_state, cx)
});
(layout_id, None)
} else {
let (layout_id, frame_state) = self.element.as_mut().unwrap().request_layout(None, cx);
(layout_id, Some(frame_state))
};
self.phase = ElementDrawPhase::LayoutRequested {
layout_id,
frame_state,
};
layout_id
}
fn paint(mut self, cx: &mut ElementContext) -> Option<E::State> {
match self.phase {
ElementDrawPhase::LayoutRequested {
fn after_layout(&mut self, cx: &mut ElementContext) {
match mem::take(&mut self.phase) {
ElementDrawPhase::BeforeLayout {
layout_id,
frame_state,
mut before_layout,
}
| ElementDrawPhase::LayoutComputed {
layout_id,
frame_state,
mut before_layout,
..
} => {
let bounds = cx.layout_bounds(layout_id);
if let Some(mut frame_state) = frame_state {
self.element
.take()
.unwrap()
.paint(bounds, &mut frame_state, cx);
Some(frame_state)
} else {
let element_id = self
.element
.as_ref()
.unwrap()
.element_id()
.expect("if we don't have frame state, we should have element state");
cx.with_element_state(element_id, |element_state, cx| {
let mut element_state = element_state.unwrap();
self.element
.take()
.unwrap()
.paint(bounds, &mut element_state, cx);
((), element_state)
});
None
}
cx.layout_element(|node_id, cx| {
let after_layout = self.element.after_layout(bounds, &mut before_layout, cx);
self.phase = ElementDrawPhase::AfterLayout {
node_id,
bounds,
before_layout,
after_layout,
};
});
}
_ => panic!("must call before_layout before after_layout"),
}
}
_ => panic!("must call layout before paint"),
fn paint(&mut self, cx: &mut ElementContext) -> E::BeforeLayout {
match mem::take(&mut self.phase) {
ElementDrawPhase::AfterLayout {
node_id,
bounds,
mut before_layout,
mut after_layout,
..
} => {
cx.paint_element(node_id, |cx| {
self.element
.paint(bounds, &mut before_layout, &mut after_layout, cx);
});
self.phase = ElementDrawPhase::Painted;
before_layout
}
_ => panic!("must call after_layout before paint"),
}
}
@@ -349,66 +327,63 @@ impl<E: Element> DrawableElement<E> {
cx: &mut ElementContext,
) -> Size<Pixels> {
if matches!(&self.phase, ElementDrawPhase::Start) {
self.request_layout(cx);
self.before_layout(cx);
}
let layout_id = match &mut self.phase {
ElementDrawPhase::LayoutRequested {
let layout_id = match mem::take(&mut self.phase) {
ElementDrawPhase::BeforeLayout {
layout_id,
frame_state,
before_layout,
} => {
cx.compute_layout(*layout_id, available_space);
let layout_id = *layout_id;
cx.compute_layout(layout_id, available_space);
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
available_space,
frame_state: frame_state.take(),
before_layout,
};
layout_id
}
ElementDrawPhase::LayoutComputed {
layout_id,
available_space: prev_available_space,
..
before_layout,
} => {
if available_space != *prev_available_space {
cx.compute_layout(*layout_id, available_space);
*prev_available_space = available_space;
if available_space != prev_available_space {
cx.compute_layout(layout_id, available_space);
}
*layout_id
self.phase = ElementDrawPhase::LayoutComputed {
layout_id,
available_space,
before_layout,
};
layout_id
}
_ => panic!("cannot measure after painting"),
};
cx.layout_bounds(layout_id).size
}
fn draw(
mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> Option<E::State> {
self.measure(available_space, cx);
cx.with_absolute_element_offset(origin, |cx| self.paint(cx))
}
}
impl<E> ElementObject for Option<DrawableElement<E>>
impl<E> ElementObject for Drawable<E>
where
E: Element,
E::State: 'static,
E::BeforeLayout: 'static,
{
fn element_id(&self) -> Option<ElementId> {
self.as_ref().unwrap().element_id()
fn inner_element(&mut self) -> &mut dyn Any {
&mut self.element
}
fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
DrawableElement::request_layout(self.as_mut().unwrap(), cx)
fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
Drawable::before_layout(self, cx)
}
fn after_layout(&mut self, cx: &mut ElementContext) {
Drawable::after_layout(self, cx);
}
fn paint(&mut self, cx: &mut ElementContext) {
DrawableElement::paint(self.take().unwrap(), cx);
Drawable::paint(self, cx);
}
fn measure(
@@ -416,16 +391,7 @@ where
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) -> Size<Pixels> {
DrawableElement::measure(self.as_mut().unwrap(), available_space, cx)
}
fn draw(
&mut self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) {
DrawableElement::draw(self.take().unwrap(), origin, available_space, cx);
Drawable::measure(self, available_space, cx)
}
}
@@ -436,18 +402,28 @@ impl AnyElement {
pub(crate) fn new<E>(element: E) -> Self
where
E: 'static + Element,
E::State: Any,
E::BeforeLayout: Any,
{
let element = ELEMENT_ARENA
.with_borrow_mut(|arena| arena.alloc(|| Some(DrawableElement::new(element))))
.with_borrow_mut(|arena| arena.alloc(|| Drawable::new(element)))
.map(|element| element as &mut dyn ElementObject);
AnyElement(element)
}
/// Attempt to downcast a reference to the boxed element to a specific type.
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.0.inner_element().downcast_mut()
}
/// Request the layout ID of the element stored in this `AnyElement`.
/// Used for laying out child elements in a parent element.
pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
self.0.request_layout(cx)
pub fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
self.0.before_layout(cx)
}
/// Commits the element bounds of this [AnyElement] for hitbox purposes.
pub fn after_layout(&mut self, cx: &mut ElementContext) {
self.0.after_layout(cx)
}
/// Paints the element stored in this `AnyElement`.
@@ -464,35 +440,44 @@ impl AnyElement {
self.0.measure(available_space, cx)
}
/// Initializes this element and performs layout in the available space, then paints it at the given origin.
pub fn draw(
/// Initializes this element, performs layout if needed and commits its bounds.
pub fn layout(
&mut self,
origin: Point<Pixels>,
absolute_offset: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) {
self.0.draw(origin, available_space, cx)
}
/// Returns the element ID of the element stored in this `AnyElement`, if any.
pub fn inner_id(&self) -> Option<ElementId> {
self.0.element_id()
) -> Size<Pixels> {
let size = self.measure(available_space, cx);
cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx));
size
}
}
impl Element for AnyElement {
type State = ();
type BeforeLayout = ();
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
let layout_id = self.request_layout(cx);
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self.before_layout(cx);
(layout_id, ())
}
fn paint(&mut self, _: Bounds<Pixels>, _: &mut Self::State, cx: &mut ElementContext) {
fn after_layout(
&mut self,
_: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
self.after_layout(cx)
}
fn paint(
&mut self,
_: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
self.paint(cx)
}
}
@@ -500,10 +485,6 @@ impl Element for AnyElement {
impl IntoElement for AnyElement {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@@ -514,35 +495,37 @@ impl IntoElement for AnyElement {
}
/// The empty element, which renders nothing.
pub type Empty = ();
pub struct Empty;
impl IntoElement for () {
impl IntoElement for Empty {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
}
impl Element for () {
type State = ();
impl Element for Empty {
type BeforeLayout = ();
type AfterLayout = ();
fn request_layout(
&mut self,
_state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
(cx.request_layout(&crate::Style::default(), None), ())
}
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) {
}
fn paint(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::State,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
}

View File

@@ -4,54 +4,68 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
/// Construct a canvas element with the given paint callback.
/// Useful for adding short term custom drawing to a view.
pub fn canvas(callback: impl 'static + FnOnce(&Bounds<Pixels>, &mut ElementContext)) -> Canvas {
pub fn canvas<T>(
after_layout: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
) -> Canvas<T> {
Canvas {
paint_callback: Some(Box::new(callback)),
after_layout: Some(Box::new(after_layout)),
paint: Some(Box::new(paint)),
style: StyleRefinement::default(),
}
}
/// A canvas element, meant for accessing the low level paint API without defining a whole
/// custom element
pub struct Canvas {
paint_callback: Option<Box<dyn FnOnce(&Bounds<Pixels>, &mut ElementContext)>>,
pub struct Canvas<T> {
after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
style: StyleRefinement,
}
impl IntoElement for Canvas {
impl<T: 'static> IntoElement for Canvas<T> {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
}
impl Element for Canvas {
type State = Style;
impl<T: 'static> Element for Canvas<T> {
type BeforeLayout = Style;
type AfterLayout = Option<T>;
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (crate::LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
let mut style = Style::default();
style.refine(&self.style);
let layout_id = cx.request_layout(&style, []);
(layout_id, style)
}
fn paint(&mut self, bounds: Bounds<Pixels>, style: &mut Style, cx: &mut ElementContext) {
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
_before_layout: &mut Style,
cx: &mut ElementContext,
) -> Option<T> {
Some(self.after_layout.take().unwrap()(bounds, cx))
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
style: &mut Style,
after_layout: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let after_layout = after_layout.take().unwrap();
style.paint(bounds, cx, |cx| {
(self.paint_callback.take().unwrap())(&bounds, cx)
(self.paint.take().unwrap())(bounds, after_layout, cx)
});
}
}
impl Styled for Canvas {
impl<T> Styled for Canvas<T> {
fn style(&mut self) -> &mut crate::StyleRefinement {
&mut self.style
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@ use std::path::PathBuf;
use std::sync::Arc;
use crate::{
point, size, Bounds, DevicePixels, Element, ElementContext, ImageData, InteractiveElement,
InteractiveElementState, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
point, size, Bounds, DevicePixels, Element, ElementContext, Hitbox, ImageData,
InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, SharedUri, Size,
StyleRefinement, Styled, UriOrPath,
};
use futures::FutureExt;
@@ -88,86 +88,85 @@ impl Img {
}
impl Element for Img {
type State = InteractiveElementState;
type BeforeLayout = ();
type AfterLayout = Option<Hitbox>;
fn request_layout(
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self
.interactivity
.before_layout(cx, |style, cx| cx.request_layout(&style, []));
(layout_id, ())
}
fn after_layout(
&mut self,
element_state: Option<Self::State>,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
) -> Option<Hitbox> {
self.interactivity
.layout(element_state, cx, |style, cx| cx.request_layout(&style, []))
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
_: &mut Self::BeforeLayout,
hitbox: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let source = self.source.clone();
self.interactivity.paint(
bounds,
bounds.size,
element_state,
cx,
|style, _scroll_offset, cx| {
self.interactivity
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
let corner_radii = style.corner_radii.to_pixels(bounds.size, cx.rem_size());
cx.with_z_index(1, |cx| {
match source {
ImageSource::Uri(_) | ImageSource::File(_) => {
let uri_or_path: UriOrPath = match source {
ImageSource::Uri(uri) => uri.into(),
ImageSource::File(path) => path.into(),
_ => unreachable!(),
};
match source {
ImageSource::Uri(_) | ImageSource::File(_) => {
let uri_or_path: UriOrPath = match source {
ImageSource::Uri(uri) => uri.into(),
ImageSource::File(path) => path.into(),
_ => unreachable!(),
};
let image_future = cx.image_cache.get(uri_or_path.clone(), cx);
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(|result| result.ok())
{
let new_bounds = preserve_aspect_ratio(bounds, data.size());
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
.log_err();
} else {
cx.spawn(|mut cx| async move {
if image_future.await.ok().is_some() {
cx.on_next_frame(|cx| cx.refresh());
}
})
.detach();
}
}
ImageSource::Data(data) => {
let image_future = cx.image_cache.get(uri_or_path.clone(), cx);
if let Some(data) = image_future
.clone()
.now_or_never()
.and_then(|result| result.ok())
{
let new_bounds = preserve_aspect_ratio(bounds, data.size());
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
.log_err();
} else {
cx.spawn(|mut cx| async move {
if image_future.await.ok().is_some() {
cx.on_next_frame(|cx| cx.refresh());
}
})
.detach();
}
}
#[cfg(target_os = "macos")]
ImageSource::Surface(surface) => {
let size = size(surface.width().into(), surface.height().into());
let new_bounds = preserve_aspect_ratio(bounds, size);
// TODO: Add support for corner_radii and grayscale.
cx.paint_surface(new_bounds, surface);
}
};
});
},
)
ImageSource::Data(data) => {
let new_bounds = preserve_aspect_ratio(bounds, data.size());
cx.paint_image(new_bounds, corner_radii, data, self.grayscale)
.log_err();
}
#[cfg(target_os = "macos")]
ImageSource::Surface(surface) => {
let size = size(surface.width().into(), surface.height().into());
let new_bounds = preserve_aspect_ratio(bounds, size);
// TODO: Add support for corner_radii and grayscale.
cx.paint_surface(new_bounds, surface);
}
}
})
}
}
impl IntoElement for Img {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
self.interactivity.element_id.clone()
}
fn into_element(self) -> Self::Element {
self
}

View File

@@ -8,11 +8,12 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
Element, ElementContext, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
Element, ElementContext, HitboxId, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
StyleRefinement, Styled, WindowContext,
};
use collections::VecDeque;
use refineable::Refineable as _;
use smallvec::SmallVec;
use std::{cell::RefCell, ops::Range, rc::Rc};
use sum_tree::{Bias, SumTree};
use taffy::style::Overflow;
@@ -96,6 +97,13 @@ struct LayoutItemsResponse {
item_elements: VecDeque<AnyElement>,
}
/// Frame state used by the [List] element.
#[derive(Default)]
pub struct ListFrameState {
scroll_top: ListOffset,
items: SmallVec<[AnyElement; 32]>,
}
#[derive(Clone)]
enum ListItem {
Unrendered,
@@ -302,7 +310,6 @@ impl StateInner {
height: Pixels,
delta: Point<Pixels>,
cx: &mut WindowContext,
padding: Edges<Pixels>,
) {
// Drop scroll events after a reset, since we can't calculate
// the new logical scroll top without the item heights
@@ -310,6 +317,7 @@ impl StateInner {
return;
}
let padding = self.last_padding.unwrap_or_default();
let scroll_max =
(self.items.summary().height + padding.top + padding.bottom - height).max(px(0.));
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
@@ -516,13 +524,13 @@ pub struct ListOffset {
}
impl Element for List {
type State = ();
type BeforeLayout = ListFrameState;
type AfterLayout = HitboxId;
fn request_layout(
fn before_layout(
&mut self,
_state: Option<Self::State>,
cx: &mut crate::ElementContext,
) -> (crate::LayoutId, Self::State) {
) -> (crate::LayoutId, Self::BeforeLayout) {
let layout_id = match self.sizing_behavior {
ListSizingBehavior::Infer => {
let mut style = Style::default();
@@ -580,15 +588,15 @@ impl Element for List {
})
}
};
(layout_id, ())
(layout_id, ListFrameState::default())
}
fn paint(
fn after_layout(
&mut self,
bounds: Bounds<crate::Pixels>,
_state: &mut Self::State,
cx: &mut crate::ElementContext,
) {
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> HitboxId {
let state = &mut *self.state.0.borrow_mut();
state.reset = false;
@@ -615,12 +623,11 @@ impl Element for List {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
item_origin.y -= layout_response.scroll_top.offset_in_item;
for item_element in &mut layout_response.item_elements {
let item_height = item_element
.measure(layout_response.available_item_space, cx)
.height;
item_element.draw(item_origin, layout_response.available_item_space, cx);
item_origin.y += item_height;
for mut item_element in layout_response.item_elements {
let item_size =
item_element.layout(item_origin, layout_response.available_item_space, cx);
before_layout.items.push(item_element);
item_origin.y += item_size.height;
}
});
}
@@ -628,20 +635,33 @@ impl Element for List {
state.last_layout_bounds = Some(bounds);
state.last_padding = Some(padding);
cx.insert_hitbox(bounds, false).id
}
fn paint(
&mut self,
bounds: Bounds<crate::Pixels>,
before_layout: &mut Self::BeforeLayout,
hitbox_id: &mut HitboxId,
cx: &mut crate::ElementContext,
) {
cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
for item in &mut before_layout.items {
item.paint(cx);
}
});
let list_state = self.state.clone();
let height = bounds.size.height;
let scroll_top = before_layout.scroll_top;
let hitbox_id = *hitbox_id;
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
if phase == DispatchPhase::Bubble
&& bounds.contains(&event.position)
&& cx.was_top_layer(&event.position, cx.stacking_order())
{
if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
list_state.0.borrow_mut().scroll(
&layout_response.scroll_top,
&scroll_top,
height,
event.delta.pixel_delta(px(20.)),
cx,
padding,
)
}
});
@@ -651,10 +671,6 @@ impl Element for List {
impl IntoElement for List {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@@ -761,7 +777,7 @@ mod test {
cx.draw(
point(px(0.), px(0.)),
size(px(100.), px(20.)).into(),
|_| list(state.clone()).w_full().h_full().z_index(10).into_any(),
|_| list(state.clone()).w_full().h_full().into_any(),
);
// Reset

View File

@@ -9,6 +9,7 @@ use crate::{
/// The state that the overlay element uses to track its children.
pub struct OverlayState {
child_layout_ids: SmallVec<[LayoutId; 4]>,
offset: Point<Pixels>,
}
/// An overlay element that can be used to display UI that
@@ -69,17 +70,14 @@ impl ParentElement for Overlay {
}
impl Element for Overlay {
type State = OverlayState;
type BeforeLayout = OverlayState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (crate::LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
let child_layout_ids = self
.children
.iter_mut()
.map(|child| child.request_layout(cx))
.map(|child| child.before_layout(cx))
.collect::<SmallVec<_>>();
let overlay_style = Style {
@@ -90,22 +88,28 @@ impl Element for Overlay {
let layout_id = cx.request_layout(&overlay_style, child_layout_ids.iter().copied());
(layout_id, OverlayState { child_layout_ids })
(
layout_id,
OverlayState {
child_layout_ids,
offset: Point::default(),
},
)
}
fn paint(
fn after_layout(
&mut self,
bounds: crate::Bounds<crate::Pixels>,
element_state: &mut Self::State,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
if element_state.child_layout_ids.is_empty() {
if before_layout.child_layout_ids.is_empty() {
return;
}
let mut child_min = point(Pixels::MAX, Pixels::MAX);
let mut child_max = Point::default();
for child_layout_id in &element_state.child_layout_ids {
for child_layout_id in &before_layout.child_layout_ids {
let child_bounds = cx.layout_bounds(*child_layout_id);
child_min = child_min.min(&child_bounds.origin);
child_max = child_max.max(&child_bounds.lower_right());
@@ -165,25 +169,30 @@ impl Element for Overlay {
desired.origin.y = limits.origin.y;
}
let mut offset = cx.element_offset() + desired.origin - bounds.origin;
offset = point(offset.x.round(), offset.y.round());
cx.with_absolute_element_offset(offset, |cx| {
cx.break_content_mask(|cx| {
for child in &mut self.children {
child.paint(cx);
}
})
})
before_layout.offset = cx.element_offset() + desired.origin - bounds.origin;
before_layout.offset = point(
before_layout.offset.x.round(),
before_layout.offset.y.round(),
);
for child in self.children.drain(..) {
cx.defer_draw(child, before_layout.offset, 1);
}
}
fn paint(
&mut self,
_bounds: crate::Bounds<crate::Pixels>,
_before_layout: &mut Self::BeforeLayout,
_after_layout: &mut Self::AfterLayout,
_cx: &mut ElementContext,
) {
}
}
impl IntoElement for Overlay {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}

View File

@@ -1,6 +1,6 @@
use crate::{
Bounds, Element, ElementContext, ElementId, InteractiveElement, InteractiveElementState,
Interactivity, IntoElement, LayoutId, Pixels, SharedString, StyleRefinement, Styled,
Bounds, Element, ElementContext, Hitbox, InteractiveElement, Interactivity, IntoElement,
LayoutId, Pixels, SharedString, StyleRefinement, Styled,
};
use util::ResultExt;
@@ -27,28 +27,37 @@ impl Svg {
}
impl Element for Svg {
type State = InteractiveElementState;
type BeforeLayout = ();
type AfterLayout = Option<Hitbox>;
fn request_layout(
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let layout_id = self
.interactivity
.before_layout(cx, |style, cx| cx.request_layout(&style, None));
(layout_id, ())
}
fn after_layout(
&mut self,
element_state: Option<Self::State>,
bounds: Bounds<Pixels>,
_before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
self.interactivity.layout(element_state, cx, |style, cx| {
cx.request_layout(&style, None)
})
) -> Option<Hitbox> {
self.interactivity
.after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
element_state: &mut Self::State,
_before_layout: &mut Self::BeforeLayout,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
) where
Self: Sized,
{
self.interactivity
.paint(bounds, bounds.size, element_state, cx, |style, _, cx| {
.paint(bounds, hitbox.as_ref(), cx, |style, cx| {
if let Some((path, color)) = self.path.as_ref().zip(style.text.color) {
cx.paint_svg(bounds, path.clone(), color).log_err();
}
@@ -59,10 +68,6 @@ impl Element for Svg {
impl IntoElement for Svg {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
}
fn into_element(self) -> Self::Element {
self
}

View File

@@ -1,7 +1,7 @@
use crate::{
ActiveTooltip, AnyTooltip, AnyView, Bounds, DispatchPhase, Element, ElementContext, ElementId,
HighlightStyle, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
HighlightStyle, Hitbox, IntoElement, LayoutId, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
Pixels, Point, SharedString, Size, TextRun, TextStyle, WhiteSpace, WindowContext, WrappedLine,
TOOLTIP_DELAY,
};
use anyhow::anyhow;
@@ -17,30 +17,37 @@ use std::{
use util::ResultExt;
impl Element for &'static str {
type State = TextState;
type BeforeLayout = TextState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
let layout_id = state.layout(SharedString::from(*self), None, cx);
(layout_id, state)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
state.paint(bounds, self, cx)
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_text_state: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) {
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut TextState,
_: &mut (),
cx: &mut ElementContext,
) {
text_state.paint(bounds, self, cx)
}
}
impl IntoElement for &'static str {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@@ -49,41 +56,44 @@ impl IntoElement for &'static str {
impl IntoElement for String {
type Element = SharedString;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self.into()
}
}
impl Element for SharedString {
type State = TextState;
type BeforeLayout = TextState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
let layout_id = state.layout(self.clone(), None, cx);
(layout_id, state)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut TextState, cx: &mut ElementContext) {
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_text_state: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) {
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
let text_str: &str = self.as_ref();
state.paint(bounds, text_str, cx)
text_state.paint(bounds, text_str, cx)
}
}
impl IntoElement for SharedString {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@@ -138,30 +148,37 @@ impl StyledText {
}
impl Element for StyledText {
type State = TextState;
type BeforeLayout = TextState;
type AfterLayout = ();
fn request_layout(
&mut self,
_: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let mut state = TextState::default();
let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
(layout_id, state)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
state.paint(bounds, &self.text, cx)
fn after_layout(
&mut self,
_bounds: Bounds<Pixels>,
_state: &mut Self::BeforeLayout,
_cx: &mut ElementContext,
) {
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
text_state.paint(bounds, &self.text, cx)
}
}
impl IntoElement for StyledText {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
None
}
fn into_element(self) -> Self::Element {
self
}
@@ -324,8 +341,8 @@ struct InteractiveTextClickEvent {
}
#[doc(hidden)]
#[derive(Default)]
pub struct InteractiveTextState {
text_state: TextState,
mouse_down_index: Rc<Cell<Option<usize>>>,
hovered_index: Rc<Cell<Option<usize>>>,
active_tooltip: Rc<RefCell<Option<ActiveTooltip>>>,
@@ -385,179 +402,181 @@ impl InteractiveText {
}
impl Element for InteractiveText {
type State = InteractiveTextState;
type BeforeLayout = TextState;
type AfterLayout = Hitbox;
fn request_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
if let Some(InteractiveTextState {
mouse_down_index,
hovered_index,
active_tooltip,
..
}) = state
{
let (layout_id, text_state) = self.text.request_layout(None, cx);
let element_state = InteractiveTextState {
text_state,
mouse_down_index,
hovered_index,
active_tooltip,
};
(layout_id, element_state)
} else {
let (layout_id, text_state) = self.text.request_layout(None, cx);
let element_state = InteractiveTextState {
text_state,
mouse_down_index: Rc::default(),
hovered_index: Rc::default(),
active_tooltip: Rc::default(),
};
(layout_id, element_state)
}
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
self.text.before_layout(cx)
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
if let Some(click_listener) = self.click_listener.take() {
let mouse_position = cx.mouse_position();
if let Some(ix) = state.text_state.index_for_position(bounds, mouse_position) {
if self
.clickable_ranges
.iter()
.any(|range| range.contains(&ix))
&& cx.was_top_layer(&mouse_position, cx.stacking_order())
{
cx.set_cursor_style(crate::CursorStyle::PointingHand)
}
}
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> Hitbox {
self.text.after_layout(bounds, state, cx);
cx.insert_hitbox(bounds, false)
}
let text_state = state.text_state.clone();
let mouse_down = state.mouse_down_index.clone();
if let Some(mouse_down_index) = mouse_down.get() {
let clickable_ranges = mem::take(&mut self.clickable_ranges);
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
if let Some(mouse_up_index) =
text_state.index_for_position(bounds, event.position)
fn paint(
&mut self,
bounds: Bounds<Pixels>,
text_state: &mut Self::BeforeLayout,
hitbox: &mut Hitbox,
cx: &mut ElementContext,
) {
cx.with_element_state::<InteractiveTextState, _>(
Some(self.element_id.clone()),
|interactive_state, cx| {
let mut interactive_state = interactive_state.unwrap().unwrap_or_default();
if let Some(click_listener) = self.click_listener.take() {
let mouse_position = cx.mouse_position();
if let Some(ix) = text_state.index_for_position(bounds, mouse_position) {
if self
.clickable_ranges
.iter()
.any(|range| range.contains(&ix))
{
click_listener(
&clickable_ranges,
InteractiveTextClickEvent {
mouse_down_index,
mouse_up_index,
},
cx,
)
}
mouse_down.take();
cx.refresh();
}
});
} else {
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
if let Some(mouse_down_index) =
text_state.index_for_position(bounds, event.position)
{
mouse_down.set(Some(mouse_down_index));
cx.refresh();
cx.set_cursor_style(crate::CursorStyle::PointingHand, hitbox)
}
}
});
}
}
if let Some(hover_listener) = self.hover_listener.take() {
let text_state = state.text_state.clone();
let hovered_index = state.hovered_index.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
let current = hovered_index.get();
let updated = text_state.index_for_position(bounds, event.position);
if current != updated {
hovered_index.set(updated);
hover_listener(updated, event.clone(), cx);
cx.refresh();
}
}
});
}
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let active_tooltip = state.active_tooltip.clone();
let pending_mouse_down = state.mouse_down_index.clone();
let text_state = state.text_state.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
let position = text_state.index_for_position(bounds, event.position);
let is_hovered = position.is_some() && pending_mouse_down.get().is_none();
if !is_hovered {
active_tooltip.take();
return;
}
let position = position.unwrap();
let text_state = text_state.clone();
let mouse_down = interactive_state.mouse_down_index.clone();
if let Some(mouse_down_index) = mouse_down.get() {
let hitbox = hitbox.clone();
let clickable_ranges = mem::take(&mut self.clickable_ranges);
cx.on_mouse_event(move |event: &MouseUpEvent, phase, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
if let Some(mouse_up_index) =
text_state.index_for_position(bounds, event.position)
{
click_listener(
&clickable_ranges,
InteractiveTextClickEvent {
mouse_down_index,
mouse_up_index,
},
cx,
)
}
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let tooltip_builder = tooltip_builder.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
cx.update(|cx| {
let new_tooltip =
tooltip_builder(position, cx).map(|tooltip| ActiveTooltip {
tooltip: Some(AnyTooltip {
view: tooltip,
cursor_offset: cx.mouse_position(),
}),
_task: None,
});
*active_tooltip.borrow_mut() = new_tooltip;
mouse_down.take();
cx.refresh();
})
.ok();
}
});
} else {
let hitbox = hitbox.clone();
cx.on_mouse_event(move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
if let Some(mouse_down_index) =
text_state.index_for_position(bounds, event.position)
{
mouse_down.set(Some(mouse_down_index));
cx.refresh();
}
}
});
}
}
if let Some(hover_listener) = self.hover_listener.take() {
let hitbox = hitbox.clone();
let text_state = text_state.clone();
let hovered_index = interactive_state.hovered_index.clone();
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
if phase == DispatchPhase::Bubble && hitbox.is_hovered(cx) {
let current = hovered_index.get();
let updated = text_state.index_for_position(bounds, event.position);
if current != updated {
hovered_index.set(updated);
hover_listener(updated, event.clone(), cx);
cx.refresh();
}
}
});
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
tooltip: None,
_task: Some(task),
});
}
});
let active_tooltip = state.active_tooltip.clone();
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
active_tooltip.take();
});
if let Some(tooltip_builder) = self.tooltip_builder.clone() {
let hitbox = hitbox.clone();
let active_tooltip = interactive_state.active_tooltip.clone();
let pending_mouse_down = interactive_state.mouse_down_index.clone();
let text_state = text_state.clone();
if let Some(tooltip) = state
.active_tooltip
.clone()
.borrow()
.as_ref()
.and_then(|at| at.tooltip.clone())
{
cx.set_tooltip(tooltip);
}
}
cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
let position = text_state.index_for_position(bounds, event.position);
let is_hovered = position.is_some()
&& hitbox.is_hovered(cx)
&& pending_mouse_down.get().is_none();
if !is_hovered {
active_tooltip.take();
return;
}
let position = position.unwrap();
self.text.paint(bounds, &mut state.text_state, cx)
if phase != DispatchPhase::Bubble {
return;
}
if active_tooltip.borrow().is_none() {
let task = cx.spawn({
let active_tooltip = active_tooltip.clone();
let tooltip_builder = tooltip_builder.clone();
move |mut cx| async move {
cx.background_executor().timer(TOOLTIP_DELAY).await;
cx.update(|cx| {
let new_tooltip =
tooltip_builder(position, cx).map(|tooltip| {
ActiveTooltip {
tooltip: Some(AnyTooltip {
view: tooltip,
cursor_offset: cx.mouse_position(),
}),
_task: None,
}
});
*active_tooltip.borrow_mut() = new_tooltip;
cx.refresh();
})
.ok();
}
});
*active_tooltip.borrow_mut() = Some(ActiveTooltip {
tooltip: None,
_task: Some(task),
});
}
});
let active_tooltip = interactive_state.active_tooltip.clone();
cx.on_mouse_event(move |_: &MouseDownEvent, _, _| {
active_tooltip.take();
});
if let Some(tooltip) = interactive_state
.active_tooltip
.clone()
.borrow()
.as_ref()
.and_then(|at| at.tooltip.clone())
{
cx.set_tooltip(tooltip);
}
}
self.text.paint(bounds, text_state, &mut (), cx);
((), Some(interactive_state))
},
);
}
}
impl IntoElement for InteractiveText {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(self.element_id.clone())
}
fn into_element(self) -> Self::Element {
self
}

View File

@@ -6,8 +6,8 @@
use crate::{
point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, Element, ElementContext,
ElementId, InteractiveElement, InteractiveElementState, Interactivity, IntoElement, LayoutId,
Pixels, Render, Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
ElementId, Hitbox, InteractiveElement, Interactivity, IntoElement, LayoutId, Pixels, Render,
Size, StyleRefinement, Styled, View, ViewContext, WindowContext,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
@@ -42,13 +42,13 @@ where
};
UniformList {
id: id.clone(),
item_count,
item_to_measure_index: 0,
render_items: Box::new(render_range),
interactivity: Interactivity {
element_id: Some(id),
base_style: Box::new(base_style),
occlude_mouse: true,
#[cfg(debug_assertions)]
location: Some(*core::panic::Location::caller()),
@@ -61,7 +61,6 @@ where
/// A list element for efficiently laying out and displaying a list of uniform-height elements.
pub struct UniformList {
id: ElementId,
item_count: usize,
item_to_measure_index: usize,
render_items:
@@ -70,6 +69,12 @@ pub struct UniformList {
scroll_handle: Option<UniformListScrollHandle>,
}
/// Frame state used by the [UniformList].
pub struct UniformListFrameState {
item_size: Size<Pixels>,
items: SmallVec<[AnyElement; 32]>,
}
/// A handle for controlling the scroll position of a uniform list.
/// This should be stored in your view and passed to the uniform_list on each frame.
#[derive(Clone, Default)]
@@ -97,72 +102,47 @@ impl Styled for UniformList {
}
}
#[doc(hidden)]
#[derive(Default)]
pub struct UniformListState {
interactive: InteractiveElementState,
item_size: Size<Pixels>,
}
impl Element for UniformList {
type State = UniformListState;
type BeforeLayout = UniformListFrameState;
type AfterLayout = Option<Hitbox>;
fn request_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
let max_items = self.item_count;
let item_size = state
.as_ref()
.map(|s| s.item_size)
.unwrap_or_else(|| self.measure_item(None, cx));
let item_size = self.measure_item(None, cx);
let layout_id = self.interactivity.before_layout(cx, |style, cx| {
cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
let desired_height = item_size.height * max_items;
let width = known_dimensions
.width
.unwrap_or(match available_space.width {
AvailableSpace::Definite(x) => x,
AvailableSpace::MinContent | AvailableSpace::MaxContent => item_size.width,
});
let (layout_id, interactive) =
self.interactivity
.layout(state.map(|s| s.interactive), cx, |style, cx| {
cx.request_measured_layout(
style,
move |known_dimensions, available_space, _cx| {
let desired_height = item_size.height * max_items;
let width =
known_dimensions
.width
.unwrap_or(match available_space.width {
AvailableSpace::Definite(x) => x,
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
item_size.width
}
});
let height = match available_space.height {
AvailableSpace::Definite(height) => desired_height.min(height),
AvailableSpace::MinContent | AvailableSpace::MaxContent => desired_height,
};
size(width, height)
})
});
let height = match available_space.height {
AvailableSpace::Definite(height) => desired_height.min(height),
AvailableSpace::MinContent | AvailableSpace::MaxContent => {
desired_height
}
};
size(width, height)
},
)
});
let element_state = UniformListState {
interactive,
item_size,
};
(layout_id, element_state)
(
layout_id,
UniformListFrameState {
item_size,
items: SmallVec::new(),
},
)
}
fn paint(
fn after_layout(
&mut self,
bounds: Bounds<crate::Pixels>,
element_state: &mut Self::State,
bounds: Bounds<Pixels>,
before_layout: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
let style =
self.interactivity
.compute_style(Some(bounds), &mut element_state.interactive, cx);
) -> Option<Hitbox> {
let style = self.interactivity.compute_style(None, cx);
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
@@ -172,17 +152,12 @@ impl Element for UniformList {
- point(border.right + padding.right, border.bottom + padding.bottom),
);
let item_size = element_state.item_size;
let content_size = Size {
width: padded_bounds.size.width,
height: item_size.height * self.item_count + padding.top + padding.bottom,
height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom,
};
let shared_scroll_offset = element_state
.interactive
.scroll_offset
.get_or_insert_with(Rc::default)
.clone();
let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
let item_height = self.measure_item(Some(padded_bounds.size.width), cx).height;
let shared_scroll_to_item = self
@@ -190,12 +165,11 @@ impl Element for UniformList {
.as_mut()
.and_then(|handle| handle.deferred_scroll_to_item.take());
self.interactivity.paint(
self.interactivity.after_layout(
bounds,
content_size,
&mut element_state.interactive,
cx,
|style, mut scroll_offset, cx| {
|style, mut scroll_offset, hitbox, cx| {
let border = style.border_widths.to_pixels(cx.rem_size());
let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
@@ -238,36 +212,45 @@ impl Element for UniformList {
..cmp::min(last_visible_element_ix, self.item_count);
let mut items = (self.render_items)(visible_range.clone(), cx);
cx.with_z_index(1, |cx| {
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (item, ix) in items.iter_mut().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(
px(0.),
item_height * ix + scroll_offset.y + padding.top,
);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.draw(item_origin, available_space, cx);
}
});
let content_mask = ContentMask { bounds };
cx.with_content_mask(Some(content_mask), |cx| {
for (mut item, ix) in items.into_iter().zip(visible_range) {
let item_origin = padded_bounds.origin
+ point(px(0.), item_height * ix + scroll_offset.y + padding.top);
let available_space = size(
AvailableSpace::Definite(padded_bounds.size.width),
AvailableSpace::Definite(item_height),
);
item.layout(item_origin, available_space, cx);
before_layout.items.push(item);
}
});
}
hitbox
},
)
}
fn paint(
&mut self,
bounds: Bounds<crate::Pixels>,
before_layout: &mut Self::BeforeLayout,
hitbox: &mut Option<Hitbox>,
cx: &mut ElementContext,
) {
self.interactivity
.paint(bounds, hitbox.as_ref(), cx, |_, cx| {
for item in &mut before_layout.items {
item.paint(cx);
}
})
}
}
impl IntoElement for UniformList {
type Element = Self;
fn element_id(&self) -> Option<crate::ElementId> {
Some(self.id.clone())
}
fn into_element(self) -> Self::Element {
self
}

View File

@@ -828,6 +828,28 @@ where
y: self.origin.y.clone() + self.size.height.clone().half(),
}
}
/// Calculates the half perimeter of a rectangle defined by the bounds.
///
/// The half perimeter is calculated as the sum of the width and the height of the rectangle.
/// This method is generic over the type `T` which must implement the `Sub` trait to allow
/// calculation of the width and height from the bounds' origin and size, as well as the `Add` trait
/// to sum the width and height for the half perimeter.
///
/// # Examples
///
/// ```
/// # use zed::{Bounds, Point, Size};
/// let bounds = Bounds {
/// origin: Point { x: 0, y: 0 },
/// size: Size { width: 10, height: 20 },
/// };
/// let half_perimeter = bounds.half_perimeter();
/// assert_eq!(half_perimeter, 30);
/// ```
pub fn half_perimeter(&self) -> T {
self.size.width.clone() + self.size.height.clone()
}
}
impl<T: Clone + Default + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bounds<T> {
@@ -2617,6 +2639,12 @@ pub trait Half {
fn half(&self) -> Self;
}
impl Half for i32 {
fn half(&self) -> Self {
self / 2
}
}
impl Half for f32 {
fn half(&self) -> Self {
self / 2.

View File

@@ -70,6 +70,7 @@ mod app;
mod arena;
mod assets;
mod bounds_tree;
mod color;
mod element;
mod elements;

View File

@@ -1,5 +1,6 @@
use crate::{
point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext,
point, seal::Sealed, Empty, IntoElement, Keystroke, Modifiers, Pixels, Point, Render,
ViewContext,
};
use smallvec::SmallVec;
use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf};
@@ -343,7 +344,8 @@ impl ExternalPaths {
impl Render for ExternalPaths {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
// Intentionally left empty because the platform will render icons for the dragged files
// the platform will render icons for the dragged files
Empty
}
}

View File

@@ -54,11 +54,12 @@ use crate::{
KeyContext, Keymap, KeymatchResult, Keystroke, KeystrokeMatcher, WindowContext,
};
use collections::FxHashMap;
use smallvec::{smallvec, SmallVec};
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
cell::RefCell,
mem,
ops::Range,
rc::Rc,
};
@@ -68,6 +69,7 @@ pub(crate) struct DispatchNodeId(usize);
pub(crate) struct DispatchTree {
node_stack: Vec<DispatchNodeId>,
pub(crate) context_stack: Vec<KeyContext>,
view_stack: Vec<EntityId>,
nodes: Vec<DispatchNode>,
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
@@ -81,7 +83,7 @@ pub(crate) struct DispatchNode {
pub key_listeners: Vec<KeyListener>,
pub action_listeners: Vec<DispatchActionListener>,
pub context: Option<KeyContext>,
focus_id: Option<FocusId>,
pub focus_id: Option<FocusId>,
view_id: Option<EntityId>,
parent: Option<DispatchNodeId>,
}
@@ -99,6 +101,7 @@ impl DispatchTree {
Self {
node_stack: Vec::new(),
context_stack: Vec::new(),
view_stack: Vec::new(),
nodes: Vec::new(),
focusable_node_ids: FxHashMap::default(),
view_node_ids: FxHashMap::default(),
@@ -111,72 +114,113 @@ impl DispatchTree {
pub fn clear(&mut self) {
self.node_stack.clear();
self.context_stack.clear();
self.view_stack.clear();
self.nodes.clear();
self.focusable_node_ids.clear();
self.view_node_ids.clear();
self.keystroke_matchers.clear();
}
pub fn push_node(
&mut self,
context: Option<KeyContext>,
focus_id: Option<FocusId>,
view_id: Option<EntityId>,
) {
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn push_node(&mut self) -> DispatchNodeId {
let parent = self.node_stack.last().copied();
let node_id = DispatchNodeId(self.nodes.len());
self.nodes.push(DispatchNode {
parent,
focus_id,
view_id,
..Default::default()
});
self.node_stack.push(node_id);
node_id
}
if let Some(context) = context {
self.active_node().context = Some(context.clone());
pub fn move_to_next_node(&mut self) -> DispatchNodeId {
let next_node_id = DispatchNodeId(self.active_node_id().0 + 1);
let next_node_parent = self.nodes[next_node_id.0].parent;
while self.node_stack.last().copied() != next_node_parent {
self.pop_node();
}
self.node_stack.push(next_node_id);
let active_node = &self.nodes[next_node_id.0];
if let Some(view_id) = active_node.view_id {
self.view_stack.push(view_id)
}
if let Some(context) = active_node.context.clone() {
self.context_stack.push(context);
}
if let Some(focus_id) = focus_id {
self.focusable_node_ids.insert(focus_id, node_id);
}
next_node_id
}
if let Some(view_id) = view_id {
pub fn set_key_context(&mut self, context: KeyContext) {
self.active_node().context = Some(context.clone());
self.context_stack.push(context);
}
pub fn set_focus_id(&mut self, focus_id: FocusId) {
let node_id = *self.node_stack.last().unwrap();
self.nodes[node_id.0].focus_id = Some(focus_id);
self.focusable_node_ids.insert(focus_id, node_id);
}
pub fn set_view_id(&mut self, view_id: EntityId) {
if self.parent_view_id() != Some(view_id) {
let node_id = *self.node_stack.last().unwrap();
self.nodes[node_id.0].view_id = Some(view_id);
self.view_node_ids.insert(view_id, node_id);
self.view_stack.push(view_id);
}
}
pub fn parent_view_id(&self) -> Option<EntityId> {
self.view_stack.last().copied()
}
pub fn pop_node(&mut self) {
let node = &self.nodes[self.active_node_id().0];
if node.context.is_some() {
self.context_stack.pop();
}
if node.view_id.is_some() {
self.view_stack.pop();
}
self.node_stack.pop();
}
fn move_node(&mut self, source: &mut DispatchNode) {
self.push_node(source.context.take(), source.focus_id, source.view_id);
self.push_node();
if let Some(context) = source.context.take() {
self.set_key_context(context);
}
if let Some(focus_id) = source.focus_id.take() {
self.set_focus_id(focus_id);
}
if let Some(view_id) = source.view_id.take() {
self.set_view_id(view_id);
}
let target = self.active_node();
target.key_listeners = mem::take(&mut source.key_listeners);
target.action_listeners = mem::take(&mut source.action_listeners);
}
pub fn reuse_view(&mut self, view_id: EntityId, source: &mut Self) -> SmallVec<[EntityId; 8]> {
let view_source_node_id = source
.view_node_ids
.get(&view_id)
.expect("view should exist in previous dispatch tree");
let view_source_node = &mut source.nodes[view_source_node_id.0];
self.move_node(view_source_node);
let mut grafted_view_ids = smallvec![view_id];
let mut source_stack = vec![*view_source_node_id];
pub fn reuse_subtree(
&mut self,
range: Range<usize>,
source: &mut Self,
) -> SmallVec<[EntityId; 8]> {
let mut grafted_view_ids = SmallVec::new();
let mut source_stack = vec![];
for (source_node_id, source_node) in source
.nodes
.iter_mut()
.enumerate()
.skip(view_source_node_id.0 + 1)
.skip(range.start)
.take(range.len())
{
let source_node_id = DispatchNodeId(source_node_id);
while let Some(source_ancestor) = source_stack.last() {
@@ -188,14 +232,10 @@ impl DispatchTree {
}
}
if source_stack.is_empty() {
break;
} else {
source_stack.push(source_node_id);
self.move_node(source_node);
if let Some(view_id) = source_node.view_id {
grafted_view_ids.push(view_id);
}
source_stack.push(source_node_id);
self.move_node(source_node);
if let Some(view_id) = source_node.view_id {
grafted_view_ids.push(view_id);
}
}

View File

@@ -1,6 +1,6 @@
// todo!(linux): remove
// todo(linux): remove
#![cfg_attr(target_os = "linux", allow(dead_code))]
// todo!("windows"): remove
// todo("windows"): remove
#![cfg_attr(windows, allow(dead_code))]
mod app_menu;
@@ -63,7 +63,7 @@ pub(crate) fn current_platform() -> Rc<dyn Platform> {
pub(crate) fn current_platform() -> Rc<dyn Platform> {
Rc::new(LinuxPlatform::new())
}
// todo!("windows")
// todo("windows")
#[cfg(target_os = "windows")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
unimplemented!()
@@ -195,8 +195,8 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool;
fn draw(&self, scene: &Scene);
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
fn set_graphics_profiler_enabled(&self, enabled: bool);
#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {

View File

@@ -564,7 +564,7 @@ impl BladeRenderer {
}
PrimitiveBatch::Paths(paths) => {
let mut encoder = pass.with(&self.pipelines.paths);
//todo!(linux): group by texture ID
// todo(linux): group by texture ID
for path in paths {
let tile = &self.path_tiles[&path.id];
let tex_info = self.atlas.get_texture_info(tile.texture_id);

View File

@@ -1,5 +1,4 @@
mod client;
mod client_dispatcher;
mod dispatcher;
mod platform;
mod text_system;
@@ -10,4 +9,4 @@ mod x11;
pub(crate) use dispatcher::*;
pub(crate) use platform::*;
pub(crate) use text_system::*;
pub(crate) use x11::*;
// pub(crate) use x11::*;

View File

@@ -4,7 +4,6 @@ use crate::platform::PlatformWindow;
use crate::{AnyWindowHandle, DisplayId, PlatformDisplay, WindowOptions};
pub trait Client {
fn run(&self, on_finish_launching: Box<dyn FnOnce()>);
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>>;
fn display(&self, id: DisplayId) -> Option<Rc<dyn PlatformDisplay>>;
fn open_window(

View File

@@ -1,3 +0,0 @@
pub trait ClientDispatcher: Send + Sync {
fn dispatch_on_main_thread(&self);
}

View File

@@ -1,50 +1,91 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
//todo!(linux): remove
// todo(linux): remove
#![allow(unused_variables)]
use crate::platform::linux::client_dispatcher::ClientDispatcher;
use crate::{PlatformDispatcher, TaskLabel};
use async_task::Runnable;
use calloop::{
channel::{self, Sender},
timer::TimeoutAction,
EventLoop,
};
use parking::{Parker, Unparker};
use parking_lot::Mutex;
use std::{
panic,
sync::Arc,
thread,
time::{Duration, Instant},
};
use std::{thread, time::Duration};
use util::ResultExt;
struct TimerAfter {
duration: Duration,
runnable: Runnable,
}
pub(crate) struct LinuxDispatcher {
client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync>,
parker: Mutex<Parker>,
timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
main_sender: flume::Sender<Runnable>,
main_sender: Sender<Runnable>,
timer_sender: Sender<TimerAfter>,
background_sender: flume::Sender<Runnable>,
_background_thread: thread::JoinHandle<()>,
_background_threads: Vec<thread::JoinHandle<()>>,
main_thread_id: thread::ThreadId,
}
impl LinuxDispatcher {
pub fn new(
main_sender: flume::Sender<Runnable>,
client_dispatcher: &Arc<dyn ClientDispatcher + Send + Sync>,
) -> Self {
pub fn new(main_sender: Sender<Runnable>) -> Self {
let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
let background_thread = thread::spawn(move || {
profiling::register_thread!("background");
for runnable in background_receiver {
let _ignore_panic = panic::catch_unwind(|| runnable.run());
}
let thread_count = std::thread::available_parallelism()
.map(|i| i.get())
.unwrap_or(1);
let mut background_threads = (0..thread_count)
.map(|_| {
let receiver = background_receiver.clone();
std::thread::spawn(move || {
for runnable in receiver {
runnable.run();
}
})
})
.collect::<Vec<_>>();
let (timer_sender, timer_channel) = calloop::channel::channel::<TimerAfter>();
let timer_thread = std::thread::spawn(|| {
let mut event_loop: EventLoop<()> =
EventLoop::try_new().expect("Failed to initialize timer loop!");
let handle = event_loop.handle();
let timer_handle = event_loop.handle();
handle
.insert_source(timer_channel, move |e, _, _| {
if let channel::Event::Msg(timer) = e {
// This has to be in an option to satisfy the borrow checker. The callback below should only be scheduled once.
let mut runnable = Some(timer.runnable);
timer_handle
.insert_source(
calloop::timer::Timer::from_duration(timer.duration),
move |e, _, _| {
if let Some(runnable) = runnable.take() {
runnable.run();
}
TimeoutAction::Drop
},
)
.expect("Failed to start timer");
}
})
.expect("Failed to start timer thread");
event_loop.run(None, &mut (), |_| {}).log_err();
});
background_threads.push(timer_thread);
Self {
client_dispatcher: Arc::clone(client_dispatcher),
parker: Mutex::new(Parker::new()),
timed_tasks: Mutex::new(Vec::new()),
main_sender,
timer_sender,
background_sender,
_background_thread: background_thread,
_background_threads: background_threads,
main_thread_id: thread::current().id(),
}
}
@@ -60,29 +101,19 @@ impl PlatformDispatcher for LinuxDispatcher {
}
fn dispatch_on_main_thread(&self, runnable: Runnable) {
self.main_sender.send(runnable).unwrap();
self.client_dispatcher.dispatch_on_main_thread();
self.main_sender
.send(runnable)
.expect("Main thread is gone");
}
fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
let moment = Instant::now() + duration;
let mut timed_tasks = self.timed_tasks.lock();
timed_tasks.push((moment, runnable));
timed_tasks.sort_unstable_by(|(a, _), (b, _)| b.cmp(a));
self.timer_sender
.send(TimerAfter { duration, runnable })
.expect("Timer thread has died");
}
fn tick(&self, background_only: bool) -> bool {
let mut timed_tasks = self.timed_tasks.lock();
let old_count = timed_tasks.len();
while let Some(&(moment, _)) = timed_tasks.last() {
if moment <= Instant::now() {
let (_, runnable) = timed_tasks.pop().unwrap();
runnable.run();
} else {
break;
}
}
timed_tasks.len() != old_count
false
}
fn park(&self) {

View File

@@ -1,5 +1,6 @@
#![allow(unused)]
use std::cell::RefCell;
use std::env;
use std::{
path::{Path, PathBuf},
@@ -10,6 +11,7 @@ use std::{
use ashpd::desktop::file_chooser::{OpenFileRequest, SaveFileRequest};
use async_task::Runnable;
use calloop::{EventLoop, LoopHandle, LoopSignal};
use flume::{Receiver, Sender};
use futures::channel::oneshot;
use parking_lot::Mutex;
@@ -17,9 +19,7 @@ use time::UtcOffset;
use wayland_client::Connection;
use crate::platform::linux::client::Client;
use crate::platform::linux::client_dispatcher::ClientDispatcher;
use crate::platform::linux::wayland::{WaylandClient, WaylandClientDispatcher};
use crate::platform::{X11Client, X11ClientDispatcher, XcbAtoms};
use crate::platform::linux::wayland::WaylandClient;
use crate::{
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
ForegroundExecutor, Keymap, LinuxDispatcher, LinuxTextSystem, Menu, PathPromptOptions,
@@ -27,12 +27,14 @@ use crate::{
SemanticVersion, Task, WindowOptions,
};
use super::x11::X11Client;
#[derive(Default)]
pub(crate) struct Callbacks {
open_urls: Option<Box<dyn FnMut(Vec<String>)>>,
become_active: Option<Box<dyn FnMut()>>,
resign_active: Option<Box<dyn FnMut()>>,
pub(crate) quit: Option<Box<dyn FnMut()>>,
quit: Option<Box<dyn FnMut()>>,
reopen: Option<Box<dyn FnMut()>>,
event: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
app_menu_action: Option<Box<dyn FnMut(&dyn Action)>>,
@@ -41,12 +43,13 @@ pub(crate) struct Callbacks {
}
pub(crate) struct LinuxPlatformInner {
pub(crate) event_loop: RefCell<EventLoop<'static, ()>>,
pub(crate) loop_handle: Rc<LoopHandle<'static, ()>>,
pub(crate) loop_signal: LoopSignal,
pub(crate) background_executor: BackgroundExecutor,
pub(crate) foreground_executor: ForegroundExecutor,
pub(crate) main_receiver: flume::Receiver<Runnable>,
pub(crate) text_system: Arc<LinuxTextSystem>,
pub(crate) callbacks: Mutex<Callbacks>,
pub(crate) state: Mutex<LinuxPlatformState>,
pub(crate) callbacks: RefCell<Callbacks>,
}
pub(crate) struct LinuxPlatform {
@@ -54,10 +57,6 @@ pub(crate) struct LinuxPlatform {
inner: Rc<LinuxPlatformInner>,
}
pub(crate) struct LinuxPlatformState {
pub(crate) quit_requested: bool,
}
impl Default for LinuxPlatform {
fn default() -> Self {
Self::new()
@@ -69,90 +68,41 @@ impl LinuxPlatform {
let wayland_display = env::var_os("WAYLAND_DISPLAY");
let use_wayland = wayland_display.is_some() && !wayland_display.unwrap().is_empty();
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let (main_sender, main_receiver) = calloop::channel::channel::<Runnable>();
let text_system = Arc::new(LinuxTextSystem::new());
let callbacks = Mutex::new(Callbacks::default());
let state = Mutex::new(LinuxPlatformState {
quit_requested: false,
let callbacks = RefCell::new(Callbacks::default());
let event_loop = EventLoop::try_new().unwrap();
event_loop
.handle()
.insert_source(main_receiver, |event, _, _| {
if let calloop::channel::Event::Msg(runnable) = event {
runnable.run();
}
});
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender));
let inner = Rc::new(LinuxPlatformInner {
loop_handle: Rc::new(event_loop.handle()),
loop_signal: event_loop.get_signal(),
event_loop: RefCell::new(event_loop),
background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
text_system,
callbacks,
});
if use_wayland {
Self::new_wayland(main_sender, main_receiver, text_system, callbacks, state)
Self {
client: Rc::new(WaylandClient::new(Rc::clone(&inner))),
inner,
}
} else {
Self::new_x11(main_sender, main_receiver, text_system, callbacks, state)
}
}
fn new_wayland(
main_sender: Sender<Runnable>,
main_receiver: Receiver<Runnable>,
text_system: Arc<LinuxTextSystem>,
callbacks: Mutex<Callbacks>,
state: Mutex<LinuxPlatformState>,
) -> Self {
let conn = Arc::new(Connection::connect_to_env().unwrap());
let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
Arc::new(WaylandClientDispatcher::new(&conn));
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
let inner = Rc::new(LinuxPlatformInner {
background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
main_receiver,
text_system,
callbacks,
state,
});
let client = Rc::new(WaylandClient::new(Rc::clone(&inner), Arc::clone(&conn)));
Self {
client,
inner: Rc::clone(&inner),
}
}
fn new_x11(
main_sender: Sender<Runnable>,
main_receiver: Receiver<Runnable>,
text_system: Arc<LinuxTextSystem>,
callbacks: Mutex<Callbacks>,
state: Mutex<LinuxPlatformState>,
) -> Self {
let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
None,
&[xcb::Extension::Present, xcb::Extension::Xkb],
&[],
)
.unwrap();
let xkb_ver = xcb_connection
.wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
wanted_major: xcb::xkb::MAJOR_VERSION as u16,
wanted_minor: xcb::xkb::MINOR_VERSION as u16,
}))
.unwrap();
assert!(xkb_ver.supported());
let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
let xcb_connection = Arc::new(xcb_connection);
let client_dispatcher: Arc<dyn ClientDispatcher + Send + Sync> =
Arc::new(X11ClientDispatcher::new(&xcb_connection, x_root_index));
let dispatcher = Arc::new(LinuxDispatcher::new(main_sender, &client_dispatcher));
let inner = Rc::new(LinuxPlatformInner {
background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
main_receiver,
text_system,
callbacks,
state,
});
let client = Rc::new(X11Client::new(
Rc::clone(&inner),
xcb_connection,
x_root_index,
atoms,
));
Self {
client,
inner: Rc::clone(&inner),
Self {
client: X11Client::new(Rc::clone(&inner)),
inner,
}
}
}
}
@@ -171,26 +121,39 @@ impl Platform for LinuxPlatform {
}
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
self.client.run(on_finish_launching)
on_finish_launching();
self.inner
.event_loop
.borrow_mut()
.run(None, &mut (), |data| {})
.expect("Run loop failed");
let mut lock = self.inner.callbacks.borrow_mut();
if let Some(mut fun) = lock.quit.take() {
drop(lock);
fun();
let mut lock = self.inner.callbacks.borrow_mut();
lock.quit = Some(fun);
}
}
fn quit(&self) {
self.inner.state.lock().quit_requested = true;
self.inner.loop_signal.stop();
}
//todo!(linux)
// todo(linux)
fn restart(&self) {}
//todo!(linux)
// todo(linux)
fn activate(&self, ignoring_other_apps: bool) {}
//todo!(linux)
// todo(linux)
fn hide(&self) {}
//todo!(linux)
// todo(linux)
fn hide_other_apps(&self) {}
//todo!(linux)
// todo(linux)
fn unhide_other_apps(&self) {}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
@@ -201,7 +164,7 @@ impl Platform for LinuxPlatform {
self.client.display(id)
}
//todo!(linux)
// todo(linux)
fn active_window(&self) -> Option<AnyWindowHandle> {
None
}
@@ -219,7 +182,7 @@ impl Platform for LinuxPlatform {
}
fn on_open_urls(&self, callback: Box<dyn FnMut(Vec<String>)>) {
self.inner.callbacks.lock().open_urls = Some(callback);
self.inner.callbacks.borrow_mut().open_urls = Some(callback);
}
fn prompt_for_paths(
@@ -306,35 +269,35 @@ impl Platform for LinuxPlatform {
}
fn on_become_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().become_active = Some(callback);
self.inner.callbacks.borrow_mut().become_active = Some(callback);
}
fn on_resign_active(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().resign_active = Some(callback);
self.inner.callbacks.borrow_mut().resign_active = Some(callback);
}
fn on_quit(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().quit = Some(callback);
self.inner.callbacks.borrow_mut().quit = Some(callback);
}
fn on_reopen(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().reopen = Some(callback);
self.inner.callbacks.borrow_mut().reopen = Some(callback);
}
fn on_event(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.inner.callbacks.lock().event = Some(callback);
self.inner.callbacks.borrow_mut().event = Some(callback);
}
fn on_app_menu_action(&self, callback: Box<dyn FnMut(&dyn Action)>) {
self.inner.callbacks.lock().app_menu_action = Some(callback);
self.inner.callbacks.borrow_mut().app_menu_action = Some(callback);
}
fn on_will_open_app_menu(&self, callback: Box<dyn FnMut()>) {
self.inner.callbacks.lock().will_open_app_menu = Some(callback);
self.inner.callbacks.borrow_mut().will_open_app_menu = Some(callback);
}
fn on_validate_app_menu_command(&self, callback: Box<dyn FnMut(&dyn Action) -> bool>) {
self.inner.callbacks.lock().validate_app_menu_command = Some(callback);
self.inner.callbacks.borrow_mut().validate_app_menu_command = Some(callback);
}
fn os_name(&self) -> &'static str {
@@ -365,7 +328,7 @@ impl Platform for LinuxPlatform {
unimplemented!()
}
//todo!(linux)
// todo(linux)
fn set_menus(&self, menus: Vec<Menu>, keymap: &Keymap) {}
fn local_timezone(&self) -> UtcOffset {
@@ -376,18 +339,18 @@ impl Platform for LinuxPlatform {
unimplemented!()
}
//todo!(linux)
// todo(linux)
fn set_cursor_style(&self, style: CursorStyle) {}
//todo!(linux)
// todo(linux)
fn should_auto_hide_scrollbars(&self) -> bool {
false
}
//todo!(linux)
// todo(linux)
fn write_to_clipboard(&self, item: ClipboardItem) {}
//todo!(linux)
// todo(linux)
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
None
}

View File

@@ -32,7 +32,7 @@ impl LinuxTextSystem {
pub(crate) fn new() -> Self {
let mut font_system = FontSystem::new();
// todo!(linux) make font loading non-blocking
// todo(linux) make font loading non-blocking
font_system.db_mut().load_system_fonts();
Self(RwLock::new(LinuxTextSystemState {
@@ -59,7 +59,7 @@ impl PlatformTextSystem for LinuxTextSystem {
self.0.write().add_fonts(fonts)
}
// todo!(linux) ensure that this integrates with platform font loading
// todo(linux) ensure that this integrates with platform font loading
// do we need to do more than call load_system_fonts()?
fn all_font_names(&self) -> Vec<String> {
self.0
@@ -71,13 +71,13 @@ impl PlatformTextSystem for LinuxTextSystem {
.collect()
}
// todo!(linux)
// todo(linux)
fn all_font_families(&self) -> Vec<String> {
Vec::new()
}
fn font_id(&self, font: &Font) -> Result<FontId> {
// todo!(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit?
// todo(linux): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit?
let lock = self.0.upgradable_read();
if let Some(font_id) = lock.font_selections.get(font) {
Ok(*font_id)
@@ -127,13 +127,13 @@ impl PlatformTextSystem for LinuxTextSystem {
FontMetrics {
units_per_em: metrics.units_per_em as u32,
ascent: metrics.ascent,
descent: -metrics.descent, // todo!(linux) confirm this is correct
descent: -metrics.descent, // todo(linux) confirm this is correct
line_gap: metrics.leading,
underline_position: metrics.underline_offset,
underline_thickness: metrics.stroke_size,
cap_height: metrics.cap_height,
x_height: metrics.x_height,
// todo!(linux): Compute this correctly
// todo(linux): Compute this correctly
bounding_box: Bounds {
origin: point(0.0, 0.0),
size: size(metrics.max_width, metrics.ascent + metrics.descent),
@@ -146,7 +146,7 @@ impl PlatformTextSystem for LinuxTextSystem {
let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]);
let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]);
let glyph_id = glyph_id.0 as u16;
// todo!(linux): Compute this correctly
// todo(linux): Compute this correctly
// see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620
Ok(Bounds {
origin: point(0.0, 0.0),
@@ -181,7 +181,7 @@ impl PlatformTextSystem for LinuxTextSystem {
self.0.write().layout_line(text, font_size, runs)
}
// todo!(linux) Confirm that this has been superseded by the LineWrapper
// todo(linux) Confirm that this has been superseded by the LineWrapper
fn wrap_line(
&self,
text: &str,
@@ -256,7 +256,7 @@ impl LinuxTextSystemState {
}
fn is_emoji(&self, font_id: FontId) -> bool {
// todo!(linux): implement this correctly
// todo(linux): implement this correctly
self.postscript_names_by_font_id
.get(&font_id)
.map_or(false, |postscript_name| {
@@ -264,7 +264,7 @@ impl LinuxTextSystemState {
})
}
// todo!(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system
// todo(linux) both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system
fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
let font = &self.fonts[params.font_id.0];
let font_system = &mut self.font_system;
@@ -297,7 +297,7 @@ impl LinuxTextSystemState {
if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 {
Err(anyhow!("glyph bounds are empty"))
} else {
// todo!(linux) handle subpixel variants
// todo(linux) handle subpixel variants
let bitmap_size = glyph_bounds.size;
let font = &self.fonts[params.font_id.0];
let font_system = &mut self.font_system;
@@ -320,13 +320,13 @@ impl LinuxTextSystemState {
}
}
// todo!(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
// todo(linux) This is all a quick first pass, maybe we should be using cosmic_text::Buffer
#[profiling::function]
fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout {
let mut attrs_list = AttrsList::new(Attrs::new());
let mut offs = 0;
for run in font_runs {
// todo!(linux) We need to check we are doing utf properly
// todo(linux) We need to check we are doing utf properly
let font = &self.fonts[run.font_id.0];
let font = self.font_system.db().face(font.id()).unwrap();
attrs_list.add_span(
@@ -343,11 +343,11 @@ impl LinuxTextSystemState {
let layout = line.layout(
&mut self.font_system,
font_size.0,
f32::MAX, // todo!(linux) we don't have a width cause this should technically not be wrapped I believe
f32::MAX, // todo(linux) we don't have a width cause this should technically not be wrapped I believe
cosmic_text::Wrap::None,
);
let mut runs = Vec::new();
// todo!(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering
// todo(linux) what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering
let layout = layout.first().unwrap();
for glyph in &layout.glyphs {
let font_id = glyph.font_id;
@@ -358,7 +358,7 @@ impl LinuxTextSystemState {
.unwrap(),
);
let mut glyphs = SmallVec::new();
// todo!(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
// todo(linux) this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction
glyphs.push(ShapedGlyph {
id: GlyphId(glyph.glyph_id as u32),
position: point((glyph.x).into(), glyph.y.into()),

View File

@@ -4,15 +4,66 @@ use crate::{Keystroke, Modifiers};
impl Keystroke {
pub(super) fn from_xkb(state: &State, modifiers: Modifiers, keycode: Keycode) -> Self {
let mut modifiers = modifiers;
let key_utf32 = state.key_get_utf32(keycode);
let key_utf8 = state.key_get_utf8(keycode);
let key_sym = state.key_get_one_sym(keycode);
// The logic here tries to replicate the logic in `../mac/events.rs`
// "Consumed" modifiers are modifiers that have been used to translate a key, for example
// pressing "shift" and "1" on US layout produces the key `!` but "consumes" the shift.
// Notes:
// - macOS gets the key character directly ("."), xkb gives us the key name ("period")
// - macOS logic removes consumed shift modifier for symbols: "{", not "shift-{"
// - macOS logic keeps consumed shift modifiers for letters: "shift-a", not "a" or "A"
let mut handle_consumed_modifiers = true;
let key = match key_sym {
Keysym::Return => "enter".to_owned(),
Keysym::Prior => "pageup".to_owned(),
Keysym::Next => "pagedown".to_owned(),
_ => xkb::keysym_get_name(key_sym).to_lowercase(),
Keysym::comma => ",".to_owned(),
Keysym::period => ".".to_owned(),
Keysym::less => "<".to_owned(),
Keysym::greater => ">".to_owned(),
Keysym::slash => "/".to_owned(),
Keysym::question => "?".to_owned(),
Keysym::semicolon => ";".to_owned(),
Keysym::colon => ":".to_owned(),
Keysym::apostrophe => "'".to_owned(),
Keysym::quotedbl => "\"".to_owned(),
Keysym::bracketleft => "[".to_owned(),
Keysym::braceleft => "{".to_owned(),
Keysym::bracketright => "]".to_owned(),
Keysym::braceright => "}".to_owned(),
Keysym::backslash => "\\".to_owned(),
Keysym::bar => "|".to_owned(),
Keysym::grave => "`".to_owned(),
Keysym::asciitilde => "~".to_owned(),
Keysym::exclam => "!".to_owned(),
Keysym::at => "@".to_owned(),
Keysym::numbersign => "#".to_owned(),
Keysym::dollar => "$".to_owned(),
Keysym::percent => "%".to_owned(),
Keysym::asciicircum => "^".to_owned(),
Keysym::ampersand => "&".to_owned(),
Keysym::asterisk => "*".to_owned(),
Keysym::parenleft => "(".to_owned(),
Keysym::parenright => ")".to_owned(),
Keysym::minus => "-".to_owned(),
Keysym::underscore => "_".to_owned(),
Keysym::equal => "=".to_owned(),
Keysym::plus => "+".to_owned(),
_ => {
handle_consumed_modifiers = false;
xkb::keysym_get_name(key_sym).to_lowercase()
}
};
// Ignore control characters (and DEL) for the purposes of ime_key,
@@ -21,6 +72,15 @@ impl Keystroke {
&& !key_utf8.is_empty())
.then_some(key_utf8);
if handle_consumed_modifiers {
let mod_shift_index = state.get_keymap().mod_get_index(xkb::MOD_NAME_SHIFT);
let is_shift_consumed = state.mod_index_is_consumed(keycode, mod_shift_index);
if modifiers.shift && is_shift_consumed {
modifiers.shift = false;
}
}
Keystroke {
modifiers,
key,

View File

@@ -1,10 +1,8 @@
//todo!(linux): remove this once the relevant functionality has been implemented
// todo(linux): remove this once the relevant functionality has been implemented
#![allow(unused_variables)]
pub(crate) use client::*;
pub(crate) use client_dispatcher::*;
mod client;
mod client_dispatcher;
mod display;
mod window;

View File

@@ -3,10 +3,12 @@ use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
use parking_lot::Mutex;
use smol::Timer;
use calloop::timer::{TimeoutAction, Timer};
use calloop::LoopHandle;
use calloop_wayland_source::WaylandSource;
use wayland_backend::client::ObjectId;
use wayland_backend::protocol::WEnum;
use wayland_client::globals::{registry_queue_init, GlobalListContents};
use wayland_client::protocol::wl_callback::WlCallback;
use wayland_client::protocol::wl_pointer::AxisRelativeDirection;
use wayland_client::{
@@ -16,7 +18,7 @@ use wayland_client::{
wl_shm, wl_shm_pool,
wl_surface::{self, WlSurface},
},
Connection, Dispatch, EventQueue, Proxy, QueueHandle,
Connection, Dispatch, Proxy, QueueHandle,
};
use wayland_protocols::wp::fractional_scale::v1::client::{
wp_fractional_scale_manager_v1, wp_fractional_scale_v1,
@@ -39,18 +41,17 @@ use crate::{
ScrollWheelEvent, TouchPhase, WindowOptions,
};
const MIN_KEYCODE: u32 = 8; // used to convert evdev scancode to xkb scancode
/// Used to convert evdev scancode to xkb scancode
const MIN_KEYCODE: u32 = 8;
pub(crate) struct WaylandClientStateInner {
compositor: Option<wl_compositor::WlCompositor>,
buffer: Option<wl_buffer::WlBuffer>,
wm_base: Option<xdg_wm_base::XdgWmBase>,
compositor: wl_compositor::WlCompositor,
wm_base: xdg_wm_base::XdgWmBase,
viewporter: Option<wp_viewporter::WpViewporter>,
fractional_scale_manager: Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
decoration_manager: Option<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1>,
windows: Vec<(xdg_surface::XdgSurface, Rc<WaylandWindowState>)>,
platform_inner: Rc<LinuxPlatformInner>,
wl_seat: Option<wl_seat::WlSeat>,
keymap_state: Option<xkb::State>,
repeat: KeyRepeat,
modifiers: Modifiers,
@@ -59,6 +60,7 @@ pub(crate) struct WaylandClientStateInner {
button_pressed: Option<MouseButton>,
mouse_focused_window: Option<Rc<WaylandWindowState>>,
keyboard_focused_window: Option<Rc<WaylandWindowState>>,
loop_handle: Rc<LoopHandle<'static, ()>>,
}
#[derive(Clone)]
@@ -73,24 +75,40 @@ pub(crate) struct KeyRepeat {
pub(crate) struct WaylandClient {
platform_inner: Rc<LinuxPlatformInner>,
conn: Arc<Connection>,
state: WaylandClientState,
event_queue: Mutex<EventQueue<WaylandClientState>>,
qh: Arc<QueueHandle<WaylandClientState>>,
}
const WL_SEAT_VERSION: u32 = 4;
impl WaylandClient {
pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>, conn: Arc<Connection>) -> Self {
let state = WaylandClientState(Rc::new(RefCell::new(WaylandClientStateInner {
compositor: None,
buffer: None,
wm_base: None,
viewporter: None,
fractional_scale_manager: None,
decoration_manager: None,
pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
let conn = Connection::connect_to_env().unwrap();
let (globals, mut event_queue) = registry_queue_init::<WaylandClientState>(&conn).unwrap();
let qh = event_queue.handle();
globals.contents().with_list(|list| {
for global in list {
if global.interface == "wl_seat" {
globals.registry().bind::<wl_seat::WlSeat, _, _>(
global.name,
WL_SEAT_VERSION,
&qh,
(),
);
}
}
});
let mut state_inner = Rc::new(RefCell::new(WaylandClientStateInner {
compositor: globals.bind(&qh, 1..=1, ()).unwrap(),
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
windows: Vec::new(),
platform_inner: Rc::clone(&linux_platform_inner),
wl_seat: None,
keymap_state: None,
repeat: KeyRepeat {
characters_per_second: 16,
@@ -110,41 +128,28 @@ impl WaylandClient {
button_pressed: None,
mouse_focused_window: None,
keyboard_focused_window: None,
})));
let event_queue: EventQueue<WaylandClientState> = conn.new_event_queue();
let qh = event_queue.handle();
loop_handle: Rc::clone(&linux_platform_inner.loop_handle),
}));
let source = WaylandSource::new(conn, event_queue);
let mut state = WaylandClientState(Rc::clone(&state_inner));
linux_platform_inner
.loop_handle
.insert_source(source, move |_, queue, _| {
queue.dispatch_pending(&mut state)
})
.unwrap();
Self {
platform_inner: linux_platform_inner,
conn,
state,
event_queue: Mutex::new(event_queue),
state: WaylandClientState(state_inner),
qh: Arc::new(qh),
}
}
}
impl Client for WaylandClient {
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
let display = self.conn.display();
let mut eq = self.event_queue.lock();
let _registry = display.get_registry(&self.qh, ());
eq.roundtrip(&mut self.state.clone()).unwrap();
on_finish_launching();
while !self.platform_inner.state.lock().quit_requested {
eq.flush().unwrap();
eq.dispatch_pending(&mut self.state.clone()).unwrap();
if let Some(guard) = self.conn.prepare_read() {
guard.read().unwrap();
eq.dispatch_pending(&mut self.state.clone()).unwrap();
}
if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
runnable.run();
}
}
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
Vec::new()
}
@@ -160,10 +165,8 @@ impl Client for WaylandClient {
) -> Box<dyn PlatformWindow> {
let mut state = self.state.0.borrow_mut();
let wm_base = state.wm_base.as_ref().unwrap();
let compositor = state.compositor.as_ref().unwrap();
let wl_surface = compositor.create_surface(&self.qh, ());
let xdg_surface = wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
let wl_surface = state.compositor.create_surface(&self.qh, ());
let xdg_surface = state.wm_base.get_xdg_surface(&wl_surface, &self.qh, ());
let toplevel = xdg_surface.get_toplevel(&self.qh, ());
let wl_surface = Arc::new(wl_surface);
@@ -180,7 +183,7 @@ impl Client for WaylandClient {
let decoration =
decoration_manager.get_toplevel_decoration(&toplevel, &self.qh, xdg_surface.id());
// todo!(linux) - options.titlebar is lacking information required for wayland.
// todo(linux) - options.titlebar is lacking information required for wayland.
// Especially, whether a titlebar is wanted in itself.
//
// Removing the titlebar also removes the entire window frame (ie. the ability to
@@ -200,8 +203,7 @@ impl Client for WaylandClient {
wl_surface.frame(&self.qh, wl_surface.clone());
wl_surface.commit();
let window_state = Rc::new(WaylandWindowState::new(
&self.conn,
let window_state: Rc<WaylandWindowState> = Rc::new(WaylandWindowState::new(
wl_surface.clone(),
viewport,
Arc::new(toplevel),
@@ -217,62 +219,27 @@ impl Client for WaylandClient {
}
}
impl Dispatch<wl_registry::WlRegistry, ()> for WaylandClientState {
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientState {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: wl_registry::Event,
_: &(),
_: &GlobalListContents,
_: &Connection,
qh: &QueueHandle<Self>,
) {
let mut state = state.0.borrow_mut();
if let wl_registry::Event::Global {
name, interface, ..
} = event
{
match &interface[..] {
"wl_compositor" => {
let compositor =
registry.bind::<wl_compositor::WlCompositor, _, _>(name, 1, qh, ());
state.compositor = Some(compositor);
match event {
wl_registry::Event::Global {
name,
interface,
version: _,
} => {
if interface.as_str() == "wl_seat" {
registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
}
"xdg_wm_base" => {
let wm_base = registry.bind::<xdg_wm_base::XdgWmBase, _, _>(name, 1, qh, ());
state.wm_base = Some(wm_base);
}
"wl_seat" => {
let seat = registry.bind::<wl_seat::WlSeat, _, _>(name, 4, qh, ());
state.wl_seat = Some(seat);
}
"wp_fractional_scale_manager_v1" => {
let manager = registry
.bind::<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1, _, _>(
name,
1,
qh,
(),
);
state.fractional_scale_manager = Some(manager);
}
"wp_viewporter" => {
let view_porter =
registry.bind::<wp_viewporter::WpViewporter, _, _>(name, 1, qh, ());
state.viewporter = Some(view_porter);
}
"zxdg_decoration_manager_v1" => {
// Unstable and optional
let decoration_manager = registry
.bind::<zxdg_decoration_manager_v1::ZxdgDecorationManagerV1, _, _>(
name,
1,
qh,
(),
);
state.decoration_manager = Some(decoration_manager);
}
_ => {}
};
}
wl_registry::Event::GlobalRemove { name: _ } => {}
_ => {}
}
}
}
@@ -367,7 +334,7 @@ impl Dispatch<xdg_toplevel::XdgToplevel, ()> for WaylandClientState {
true
}
});
state.platform_inner.state.lock().quit_requested |= state.windows.is_empty();
state.platform_inner.loop_signal.stop();
}
}
}
@@ -515,7 +482,7 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
wl_keyboard::KeyState::Pressed => {
let input = PlatformInput::KeyDown(KeyDownEvent {
keystroke: Keystroke::from_xkb(keymap_state, state.modifiers, keycode),
is_held: false, // todo!(linux)
is_held: false, // todo(linux)
});
focused_window.handle_input(input.clone());
@@ -529,34 +496,29 @@ impl Dispatch<wl_keyboard::WlKeyboard, ()> for WaylandClientState {
let id = state.repeat.current_id;
let this = this.clone();
let timer = Timer::from_duration(delay);
let state_ = Rc::clone(&this.0);
state
.platform_inner
.foreground_executor
.spawn(async move {
let mut wait_time = delay;
.loop_handle
.insert_source(timer, move |event, _metadata, shared_data| {
let state_ = state_.borrow_mut();
let is_repeating = id == state_.repeat.current_id
&& state_.repeat.current_keysym.is_some()
&& state_.keyboard_focused_window.is_some();
loop {
Timer::after(wait_time).await;
let state = this.0.borrow_mut();
let is_repeating = id == state.repeat.current_id
&& state.repeat.current_keysym.is_some()
&& state.keyboard_focused_window.is_some();
if !is_repeating {
return;
}
state
.keyboard_focused_window
.as_ref()
.unwrap()
.handle_input(input.clone());
wait_time = Duration::from_secs(1) / rate;
if !is_repeating {
return TimeoutAction::Drop;
}
state_
.keyboard_focused_window
.as_ref()
.unwrap()
.handle_input(input.clone());
TimeoutAction::ToDuration(Duration::from_secs(1) / rate)
})
.detach();
.unwrap();
}
}
wl_keyboard::KeyState::Released => {

View File

@@ -1,30 +0,0 @@
use std::sync::Arc;
use wayland_client::{Connection, EventQueue};
use crate::platform::linux::client_dispatcher::ClientDispatcher;
pub(crate) struct WaylandClientDispatcher {
conn: Arc<Connection>,
event_queue: Arc<EventQueue<Connection>>,
}
impl WaylandClientDispatcher {
pub(crate) fn new(conn: &Arc<Connection>) -> Self {
let event_queue = conn.new_event_queue();
Self {
conn: Arc::clone(conn),
event_queue: Arc::new(event_queue),
}
}
}
impl Drop for WaylandClientDispatcher {
fn drop(&mut self) {
//todo!(linux)
}
}
impl ClientDispatcher for WaylandClientDispatcher {
fn dispatch_on_main_thread(&self) {}
}

View File

@@ -8,17 +8,17 @@ use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Size};
pub(crate) struct WaylandDisplay {}
impl PlatformDisplay for WaylandDisplay {
// todo!(linux)
// todo(linux)
fn id(&self) -> DisplayId {
DisplayId(123) // return some fake data so it doesn't panic
}
// todo!(linux)
// todo(linux)
fn uuid(&self) -> anyhow::Result<Uuid> {
Ok(Uuid::from_bytes([0; 16])) // return some fake data so it doesn't panic
}
// todo!(linux)
// todo(linux)
fn bounds(&self) -> Bounds<GlobalPixels> {
Bounds {
origin: Default::default(),

View File

@@ -1,4 +1,5 @@
use std::any::Any;
use std::cell::RefCell;
use std::ffi::c_void;
use std::rc::Rc;
use std::sync::Arc;
@@ -6,7 +7,6 @@ use std::sync::Arc;
use blade_graphics as gpu;
use blade_rwh::{HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle};
use futures::channel::oneshot::Receiver;
use parking_lot::Mutex;
use raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, WindowHandle,
};
@@ -66,14 +66,15 @@ unsafe impl HasRawDisplayHandle for RawWindow {
}
impl WaylandWindowInner {
fn new(
conn: &Arc<wayland_client::Connection>,
wl_surf: &Arc<wl_surface::WlSurface>,
bounds: Bounds<i32>,
) -> Self {
fn new(wl_surf: &Arc<wl_surface::WlSurface>, bounds: Bounds<i32>) -> Self {
let raw = RawWindow {
window: wl_surf.id().as_ptr().cast::<c_void>(),
display: conn.backend().display_ptr().cast::<c_void>(),
display: wl_surf
.backend()
.upgrade()
.unwrap()
.display_ptr()
.cast::<c_void>(),
};
let gpu = Arc::new(
unsafe {
@@ -105,9 +106,8 @@ impl WaylandWindowInner {
}
pub(crate) struct WaylandWindowState {
conn: Arc<wayland_client::Connection>,
inner: Mutex<WaylandWindowInner>,
pub(crate) callbacks: Mutex<Callbacks>,
inner: RefCell<WaylandWindowInner>,
pub(crate) callbacks: RefCell<Callbacks>,
pub(crate) surface: Arc<wl_surface::WlSurface>,
pub(crate) toplevel: Arc<xdg_toplevel::XdgToplevel>,
viewport: Option<wp_viewport::WpViewport>,
@@ -115,7 +115,6 @@ pub(crate) struct WaylandWindowState {
impl WaylandWindowState {
pub(crate) fn new(
conn: &Arc<wayland_client::Connection>,
wl_surf: Arc<wl_surface::WlSurface>,
viewport: Option<wp_viewport::WpViewport>,
toplevel: Arc<xdg_toplevel::XdgToplevel>,
@@ -133,40 +132,39 @@ impl WaylandWindowState {
size: Size {
width: 500,
height: 500,
}, //todo!(implement)
}, // todo(implement)
},
WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32),
};
Self {
conn: Arc::clone(conn),
surface: Arc::clone(&wl_surf),
inner: Mutex::new(WaylandWindowInner::new(&Arc::clone(conn), &wl_surf, bounds)),
callbacks: Mutex::new(Callbacks::default()),
inner: RefCell::new(WaylandWindowInner::new(&wl_surf, bounds)),
callbacks: RefCell::new(Callbacks::default()),
toplevel,
viewport,
}
}
pub fn update(&self) {
let mut cb = self.callbacks.lock();
let mut cb = self.callbacks.borrow_mut();
if let Some(mut fun) = cb.request_frame.take() {
drop(cb);
fun();
self.callbacks.lock().request_frame = Some(fun);
self.callbacks.borrow_mut().request_frame = Some(fun);
}
}
pub fn set_size_and_scale(&self, width: i32, height: i32, scale: f32) {
self.inner.lock().scale = scale;
self.inner.lock().bounds.size.width = width;
self.inner.lock().bounds.size.height = height;
self.inner.lock().renderer.update_drawable_size(size(
self.inner.borrow_mut().scale = scale;
self.inner.borrow_mut().bounds.size.width = width;
self.inner.borrow_mut().bounds.size.height = height;
self.inner.borrow_mut().renderer.update_drawable_size(size(
width as f64 * scale as f64,
height as f64 * scale as f64,
));
if let Some(ref mut fun) = self.callbacks.lock().resize {
if let Some(ref mut fun) = self.callbacks.borrow_mut().resize {
fun(
Size {
width: px(width as f32),
@@ -182,12 +180,12 @@ impl WaylandWindowState {
}
pub fn resize(&self, width: i32, height: i32) {
let scale = self.inner.lock().scale;
let scale = self.inner.borrow_mut().scale;
self.set_size_and_scale(width, height, scale);
}
pub fn rescale(&self, scale: f32) {
let bounds = self.inner.lock().bounds;
let bounds = self.inner.borrow_mut().bounds;
self.set_size_and_scale(bounds.size.width, bounds.size.height, scale)
}
@@ -200,13 +198,13 @@ impl WaylandWindowState {
/// of the decorations. This is because the state of the decorations
/// is managed by the compositor and not the client.
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
self.inner.lock().decoration_state = state;
self.inner.borrow_mut().decoration_state = state;
log::trace!("Window decorations are now handled by {:?}", state);
// todo!(linux) - Handle this properly
// todo(linux) - Handle this properly
}
pub fn close(&self) {
let mut callbacks = self.callbacks.lock();
let mut callbacks = self.callbacks.borrow_mut();
if let Some(fun) = callbacks.close.take() {
fun()
}
@@ -214,13 +212,13 @@ impl WaylandWindowState {
}
pub fn handle_input(&self, input: PlatformInput) {
if let Some(ref mut fun) = self.callbacks.lock().input {
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
if fun(input.clone()) {
return;
}
}
if let PlatformInput::KeyDown(event) = input {
let mut inner = self.inner.lock();
let mut inner = self.inner.borrow_mut();
if let Some(ref mut input_handler) = inner.input_handler {
if let Some(ime_key) = &event.keystroke.ime_key {
input_handler.replace_text_in_range(None, ime_key);
@@ -230,7 +228,7 @@ impl WaylandWindowState {
}
pub fn set_focused(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
fun(focus);
}
}
@@ -252,13 +250,13 @@ impl HasDisplayHandle for WaylandWindow {
}
impl PlatformWindow for WaylandWindow {
//todo!(linux)
// todo(linux)
fn bounds(&self) -> WindowBounds {
WindowBounds::Maximized
}
fn content_size(&self) -> Size<Pixels> {
let inner = self.0.inner.lock();
let inner = self.0.inner.borrow_mut();
Size {
width: Pixels(inner.bounds.size.width as f32),
height: Pixels(inner.bounds.size.height as f32),
@@ -266,48 +264,48 @@ impl PlatformWindow for WaylandWindow {
}
fn scale_factor(&self) -> f32 {
self.0.inner.lock().scale
self.0.inner.borrow_mut().scale
}
//todo!(linux)
// todo(linux)
fn titlebar_height(&self) -> Pixels {
unimplemented!()
}
// todo!(linux)
// todo(linux)
fn appearance(&self) -> WindowAppearance {
WindowAppearance::Light
}
// todo!(linux)
// todo(linux)
fn display(&self) -> Rc<dyn PlatformDisplay> {
Rc::new(WaylandDisplay {})
}
// todo!(linux)
// todo(linux)
fn mouse_position(&self) -> Point<Pixels> {
Point::default()
}
//todo!(linux)
// todo(linux)
fn modifiers(&self) -> Modifiers {
crate::Modifiers::default()
}
//todo!(linux)
// todo(linux)
fn as_any_mut(&mut self) -> &mut dyn Any {
unimplemented!()
}
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.0.inner.lock().input_handler = Some(input_handler);
self.0.inner.borrow_mut().input_handler = Some(input_handler);
}
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.0.inner.lock().input_handler.take()
self.0.inner.borrow_mut().input_handler.take()
}
//todo!(linux)
// todo(linux)
fn prompt(
&self,
level: PromptLevel,
@@ -319,7 +317,7 @@ impl PlatformWindow for WaylandWindow {
}
fn activate(&self) {
//todo!(linux)
// todo(linux)
}
fn set_title(&mut self, title: &str) {
@@ -327,79 +325,75 @@ impl PlatformWindow for WaylandWindow {
}
fn set_edited(&mut self, edited: bool) {
//todo!(linux)
// todo(linux)
}
fn show_character_palette(&self) {
//todo!(linux)
// todo(linux)
}
fn minimize(&self) {
//todo!(linux)
// todo(linux)
}
fn zoom(&self) {
//todo!(linux)
// todo(linux)
}
fn toggle_full_screen(&self) {
//todo!(linux)
// todo(linux)
}
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.lock().request_frame = Some(callback);
self.0.callbacks.borrow_mut().request_frame = Some(callback);
}
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.0.callbacks.lock().input = Some(callback);
self.0.callbacks.borrow_mut().input = Some(callback);
}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.lock().active_status_change = Some(callback);
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.lock().resize = Some(callback);
self.0.callbacks.borrow_mut().resize = Some(callback);
}
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
//todo!(linux)
// todo(linux)
}
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.lock().moved = Some(callback);
self.0.callbacks.borrow_mut().moved = Some(callback);
}
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
self.0.callbacks.lock().should_close = Some(callback);
self.0.callbacks.borrow_mut().should_close = Some(callback);
}
fn on_close(&self, callback: Box<dyn FnOnce()>) {
self.0.callbacks.lock().close = Some(callback);
self.0.callbacks.borrow_mut().close = Some(callback);
}
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
//todo!(linux)
// todo(linux)
}
// todo!(linux)
// todo(linux)
fn is_topmost_for_position(&self, position: Point<Pixels>) -> bool {
false
}
fn draw(&self, scene: &Scene) {
let mut inner = self.0.inner.lock();
let mut inner = self.0.inner.borrow_mut();
inner.renderer.draw(scene);
}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
let inner = self.0.inner.lock();
let inner = self.0.inner.borrow_mut();
inner.renderer.sprite_atlas().clone()
}
fn set_graphics_profiler_enabled(&self, enabled: bool) {
//todo!(linux)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]

View File

@@ -1,11 +1,9 @@
mod client;
mod client_dispatcher;
mod display;
mod event;
mod window;
pub(crate) use client::*;
pub(crate) use client_dispatcher::*;
pub(crate) use display::*;
pub(crate) use event::*;
pub(crate) use window::*;

View File

@@ -1,40 +1,60 @@
use std::{rc::Rc, sync::Arc};
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::time::Duration;
use parking_lot::Mutex;
use xcb::{x, Xid as _};
use xkbcommon::xkb;
use collections::{HashMap, HashSet};
use crate::platform::linux::client::Client;
use crate::platform::{
LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
};
use crate::platform::{LinuxPlatformInner, PlatformWindow};
use crate::{
AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size,
TouchPhase, WindowOptions,
};
use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
use calloop::generic::{FdWrapper, Generic};
pub(crate) struct X11ClientState {
pub(crate) windows: HashMap<x::Window, Rc<X11WindowState>>,
pub(crate) windows_to_refresh: HashSet<x::Window>,
xkb: xkbcommon::xkb::State,
}
pub(crate) struct X11Client {
platform_inner: Rc<LinuxPlatformInner>,
xcb_connection: Arc<xcb::Connection>,
xcb_connection: Rc<xcb::Connection>,
x_root_index: i32,
atoms: XcbAtoms,
state: Mutex<X11ClientState>,
refresh_millis: Cell<u64>,
state: RefCell<X11ClientState>,
}
impl X11Client {
pub(crate) fn new(
inner: Rc<LinuxPlatformInner>,
xcb_connection: Arc<xcb::Connection>,
x_root_index: i32,
atoms: XcbAtoms,
) -> Self {
pub(crate) fn new(inner: Rc<LinuxPlatformInner>) -> Rc<Self> {
let (xcb_connection, x_root_index) = xcb::Connection::connect_with_extensions(
None,
&[
xcb::Extension::Present,
xcb::Extension::Xkb,
xcb::Extension::RandR,
],
&[],
)
.unwrap();
let xkb_ver = xcb_connection
.wait_for_reply(xcb_connection.send_request(&xcb::xkb::UseExtension {
wanted_major: xcb::xkb::MAJOR_VERSION as u16,
wanted_minor: xcb::xkb::MINOR_VERSION as u16,
}))
.unwrap();
assert!(xkb_ver.supported());
let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
let xcb_connection = Rc::new(xcb_connection);
let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
let xkb_device_id = xkb::x11::get_core_keyboard_device_id(&xcb_connection);
let xkb_keymap = xkb::x11::keymap_new_from_device(
@@ -43,195 +63,235 @@ impl X11Client {
xkb_device_id,
xkb::KEYMAP_COMPILE_NO_FLAGS,
);
let xkb_state =
xkb::x11::state_new_from_device(&xkb_keymap, &xcb_connection, xkb_device_id);
Self {
platform_inner: inner,
xcb_connection,
let client: Rc<X11Client> = Rc::new(Self {
platform_inner: inner.clone(),
xcb_connection: xcb_connection.clone(),
x_root_index,
atoms,
state: Mutex::new(X11ClientState {
refresh_millis: Cell::new(16),
state: RefCell::new(X11ClientState {
windows: HashMap::default(),
windows_to_refresh: HashSet::default(),
xkb: xkb_state,
}),
});
// Safety: Safe if xcb::Connection always returns a valid fd
let fd = unsafe { FdWrapper::new(xcb_connection.clone()) };
inner
.loop_handle
.insert_source(
Generic::new_with_error::<xcb::Error>(
fd,
calloop::Interest::READ,
calloop::Mode::Level,
),
{
let client = client.clone();
move |readiness, _, _| {
if readiness.readable || readiness.error {
while let Some(event) = xcb_connection.poll_for_event()? {
client.handle_event(event);
}
}
Ok(calloop::PostAction::Continue)
}
},
)
.expect("Failed to initialize x11 event source");
inner
.loop_handle
.insert_source(
calloop::timer::Timer::from_duration(Duration::from_millis(
client.refresh_millis.get(),
)),
{
let client = client.clone();
move |_, _, _| {
client.present();
calloop::timer::TimeoutAction::ToDuration(Duration::from_millis(
client.refresh_millis.get(),
))
}
},
)
.expect("Failed to initialize refresh timer");
client
}
fn get_window(&self, win: x::Window) -> Option<Rc<X11WindowState>> {
let state = self.state.borrow();
state.windows.get(&win).cloned()
}
fn present(&self) {
let state = self.state.borrow_mut();
for window_state in state.windows.values() {
window_state.refresh();
}
}
fn get_window(&self, win: x::Window) -> Rc<X11WindowState> {
let state = self.state.lock();
Rc::clone(&state.windows[&win])
fn handle_event(&self, event: xcb::Event) -> Option<()> {
match event {
xcb::Event::X(x::Event::ClientMessage(event)) => {
if let x::ClientMessageData::Data32([atom, ..]) = event.data() {
if atom == self.atoms.wm_del_window.resource_id() {
self.state
.borrow_mut()
.windows_to_refresh
.remove(&event.window());
// window "x" button clicked by user, we gracefully exit
let window = self
.state
.borrow_mut()
.windows
.remove(&event.window())
.unwrap();
window.destroy();
let state = self.state.borrow();
if state.windows.is_empty() {
self.platform_inner.loop_signal.stop();
}
}
}
}
xcb::Event::X(x::Event::ConfigureNotify(event)) => {
let bounds = Bounds {
origin: Point {
x: event.x().into(),
y: event.y().into(),
},
size: Size {
width: event.width().into(),
height: event.height().into(),
},
};
let window = self.get_window(event.window())?;
window.configure(bounds);
}
xcb::Event::X(x::Event::FocusIn(event)) => {
let window = self.get_window(event.event())?;
window.set_focused(true);
}
xcb::Event::X(x::Event::FocusOut(event)) => {
let window = self.get_window(event.event())?;
window.set_focused(false);
}
xcb::Event::X(x::Event::KeyPress(event)) => {
let window = self.get_window(event.event())?;
let modifiers = super::modifiers_from_state(event.state());
let keystroke = {
let code = event.detail().into();
let mut state = self.state.borrow_mut();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkb::KeyDirection::Down);
keystroke
};
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
}));
}
xcb::Event::X(x::Event::KeyRelease(event)) => {
let window = self.get_window(event.event())?;
let modifiers = super::modifiers_from_state(event.state());
let keystroke = {
let code = event.detail().into();
let mut state = self.state.borrow_mut();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkb::KeyDirection::Up);
keystroke
};
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
}
xcb::Event::X(x::Event::ButtonPress(event)) => {
let window = self.get_window(event.event())?;
let modifiers = super::modifiers_from_state(event.state());
let position = Point::new(
(event.event_x() as f32).into(),
(event.event_y() as f32).into(),
);
if let Some(button) = super::button_of_key(event.detail()) {
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
button,
position,
modifiers,
click_count: 1,
}));
} else if event.detail() >= 4 && event.detail() <= 5 {
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
let delta_x = if event.detail() == 4 { 1.0 } else { -1.0 };
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
modifiers,
touch_phase: TouchPhase::default(),
}));
} else {
log::warn!("Unknown button press: {event:?}");
}
}
xcb::Event::X(x::Event::ButtonRelease(event)) => {
let window = self.get_window(event.event())?;
let modifiers = super::modifiers_from_state(event.state());
let position = Point::new(
(event.event_x() as f32).into(),
(event.event_y() as f32).into(),
);
if let Some(button) = super::button_of_key(event.detail()) {
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
button,
position,
modifiers,
click_count: 1,
}));
}
}
xcb::Event::X(x::Event::MotionNotify(event)) => {
let window = self.get_window(event.event())?;
let pressed_button = super::button_from_state(event.state());
let position = Point::new(
(event.event_x() as f32).into(),
(event.event_y() as f32).into(),
);
let modifiers = super::modifiers_from_state(event.state());
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
pressed_button,
position,
modifiers,
}));
}
xcb::Event::X(x::Event::LeaveNotify(event)) => {
let window = self.get_window(event.event())?;
let pressed_button = super::button_from_state(event.state());
let position = Point::new(
(event.event_x() as f32).into(),
(event.event_y() as f32).into(),
);
let modifiers = super::modifiers_from_state(event.state());
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button,
position,
modifiers,
}));
}
_ => {}
};
Some(())
}
}
impl Client for X11Client {
fn run(&self, on_finish_launching: Box<dyn FnOnce()>) {
on_finish_launching();
let mut windows_to_refresh = HashSet::<x::Window>::default();
while !self.platform_inner.state.lock().quit_requested {
// We prioritize work in the following order:
// 1. input events from X11
// 2. runnables for the main thread
// 3. drawing/presentation
let event = if let Some(event) = self.xcb_connection.poll_for_event().unwrap() {
event
} else if let Ok(runnable) = self.platform_inner.main_receiver.try_recv() {
runnable.run();
continue;
} else if let Some(x_window) = windows_to_refresh.iter().next().cloned() {
windows_to_refresh.remove(&x_window);
let window = self.get_window(x_window);
window.refresh();
window.request_refresh();
continue;
} else {
profiling::scope!("Wait for event");
self.xcb_connection.wait_for_event().unwrap()
};
match event {
xcb::Event::X(x::Event::ClientMessage(ev)) => {
if let x::ClientMessageData::Data32([atom, ..]) = ev.data() {
if atom == self.atoms.wm_del_window.resource_id() {
windows_to_refresh.remove(&ev.window());
// window "x" button clicked by user, we gracefully exit
let window = self.state.lock().windows.remove(&ev.window()).unwrap();
window.destroy();
let state = self.state.lock();
self.platform_inner.state.lock().quit_requested |=
state.windows.is_empty();
}
}
}
xcb::Event::X(x::Event::Expose(ev)) => {
windows_to_refresh.insert(ev.window());
}
xcb::Event::X(x::Event::ConfigureNotify(ev)) => {
let bounds = Bounds {
origin: Point {
x: ev.x().into(),
y: ev.y().into(),
},
size: Size {
width: ev.width().into(),
height: ev.height().into(),
},
};
self.get_window(ev.window()).configure(bounds)
}
xcb::Event::Present(xcb::present::Event::CompleteNotify(ev)) => {
windows_to_refresh.insert(ev.window());
}
xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
xcb::Event::X(x::Event::FocusIn(ev)) => {
let window = self.get_window(ev.event());
window.set_focused(true);
}
xcb::Event::X(x::Event::FocusOut(ev)) => {
let window = self.get_window(ev.event());
window.set_focused(false);
}
xcb::Event::X(x::Event::KeyPress(ev)) => {
let window = self.get_window(ev.event());
let modifiers = super::modifiers_from_state(ev.state());
let keystroke = {
let code = ev.detail().into();
let mut state = self.state.lock();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkb::KeyDirection::Down);
keystroke
};
window.handle_input(PlatformInput::KeyDown(crate::KeyDownEvent {
keystroke,
is_held: false,
}));
}
xcb::Event::X(x::Event::KeyRelease(ev)) => {
let window = self.get_window(ev.event());
let modifiers = super::modifiers_from_state(ev.state());
let keystroke = {
let code = ev.detail().into();
let mut state = self.state.lock();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
state.xkb.update_key(code, xkb::KeyDirection::Up);
keystroke
};
window.handle_input(PlatformInput::KeyUp(crate::KeyUpEvent { keystroke }));
}
xcb::Event::X(x::Event::ButtonPress(ev)) => {
let window = self.get_window(ev.event());
let modifiers = super::modifiers_from_state(ev.state());
let position =
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
if let Some(button) = super::button_of_key(ev.detail()) {
window.handle_input(PlatformInput::MouseDown(crate::MouseDownEvent {
button,
position,
modifiers,
click_count: 1,
}));
} else if ev.detail() >= 4 && ev.detail() <= 5 {
// https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
let delta_x = if ev.detail() == 4 { 1.0 } else { -1.0 };
window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
position,
delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
modifiers,
touch_phase: TouchPhase::default(),
}));
} else {
log::warn!("Unknown button press: {ev:?}");
}
}
xcb::Event::X(x::Event::ButtonRelease(ev)) => {
let window = self.get_window(ev.event());
let modifiers = super::modifiers_from_state(ev.state());
let position =
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
if let Some(button) = super::button_of_key(ev.detail()) {
window.handle_input(PlatformInput::MouseUp(crate::MouseUpEvent {
button,
position,
modifiers,
click_count: 1,
}));
}
}
xcb::Event::X(x::Event::MotionNotify(ev)) => {
let window = self.get_window(ev.event());
let pressed_button = super::button_from_state(ev.state());
let position =
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
let modifiers = super::modifiers_from_state(ev.state());
window.handle_input(PlatformInput::MouseMove(crate::MouseMoveEvent {
pressed_button,
position,
modifiers,
}));
}
xcb::Event::X(x::Event::LeaveNotify(ev)) => {
let window = self.get_window(ev.event());
let pressed_button = super::button_from_state(ev.state());
let position =
Point::new((ev.event_x() as f32).into(), (ev.event_y() as f32).into());
let modifiers = super::modifiers_from_state(ev.state());
window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
pressed_button,
position,
modifiers,
}));
}
_ => {}
}
}
if let Some(ref mut fun) = self.platform_inner.callbacks.lock().quit {
fun();
}
}
fn displays(&self) -> Vec<Rc<dyn PlatformDisplay>> {
let setup = self.xcb_connection.get_setup();
setup
@@ -264,10 +324,40 @@ impl Client for X11Client {
));
window_ptr.request_refresh();
let cookie = self
.xcb_connection
.send_request(&xcb::randr::GetScreenResourcesCurrent { window: x_window });
let screen_resources = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
let crtc = screen_resources.crtcs().first().expect("TODO");
let cookie = self.xcb_connection.send_request(&xcb::randr::GetCrtcInfo {
crtc: crtc.to_owned(),
config_timestamp: xcb::x::Time::CurrentTime as u32,
});
let crtc_info = self.xcb_connection.wait_for_reply(cookie).expect("TODO");
let mode_id = crtc_info.mode().resource_id();
let mode = screen_resources
.modes()
.iter()
.find(|m| m.id == mode_id)
.expect("Missing screen mode for crtc specified mode id");
let refresh_millies = mode_refresh_rate_millis(mode);
self.refresh_millis.set(refresh_millies);
self.state
.lock()
.borrow_mut()
.windows
.insert(x_window, Rc::clone(&window_ptr));
Box::new(X11Window(window_ptr))
}
}
// Adatpted from:
// https://docs.rs/winit/0.29.11/src/winit/platform_impl/linux/x11/monitor.rs.html#103-111
pub fn mode_refresh_rate_millis(mode: &xcb::randr::ModeInfo) -> u64 {
let millihertz = mode.dot_clock as u64 * 1_000 / (mode.htotal as u64 * mode.vtotal as u64);
(millihertz as f64 / 1_000_000.) as u64
}

View File

@@ -1,64 +0,0 @@
use std::sync::Arc;
use xcb::x;
use crate::platform::linux::client_dispatcher::ClientDispatcher;
pub(crate) struct X11ClientDispatcher {
xcb_connection: Arc<xcb::Connection>,
x_listener_window: x::Window,
}
impl X11ClientDispatcher {
pub fn new(xcb_connection: &Arc<xcb::Connection>, x_root_index: i32) -> Self {
let x_listener_window = xcb_connection.generate_id();
let screen = xcb_connection
.get_setup()
.roots()
.nth(x_root_index as usize)
.unwrap();
xcb_connection.send_request(&x::CreateWindow {
depth: 0,
wid: x_listener_window,
parent: screen.root(),
x: 0,
y: 0,
width: 1,
height: 1,
border_width: 0,
class: x::WindowClass::InputOnly,
visual: screen.root_visual(),
value_list: &[],
});
Self {
xcb_connection: Arc::clone(xcb_connection),
x_listener_window,
}
}
}
impl Drop for X11ClientDispatcher {
fn drop(&mut self) {
self.xcb_connection.send_request(&x::DestroyWindow {
window: self.x_listener_window,
});
}
}
impl ClientDispatcher for X11ClientDispatcher {
fn dispatch_on_main_thread(&self) {
// Send a message to the invisible window, forcing
// the main loop to wake up and dispatch the runnable.
self.xcb_connection.send_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.x_listener_window),
event_mask: x::EventMask::NO_EVENT,
event: &x::VisibilityNotifyEvent::new(
self.x_listener_window,
x::Visibility::Unobscured,
),
});
self.xcb_connection.flush().unwrap();
}
}

View File

@@ -1,10 +1,10 @@
//todo!(linux): remove
// todo(linux): remove
#![allow(unused)]
use crate::{
platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
Scene, Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
Scene, Size, WindowAppearance, WindowBounds, WindowOptions,
};
use blade_graphics as gpu;
use parking_lot::Mutex;
@@ -16,6 +16,7 @@ use xcb::{
};
use std::{
cell::RefCell,
ffi::c_void,
mem,
num::NonZeroU32,
@@ -24,6 +25,8 @@ use std::{
sync::{self, Arc},
};
use super::X11Display;
#[derive(Default)]
struct Callbacks {
request_frame: Option<Box<dyn FnMut()>>,
@@ -85,18 +88,18 @@ struct RawWindow {
}
pub(crate) struct X11WindowState {
xcb_connection: Arc<xcb::Connection>,
xcb_connection: Rc<xcb::Connection>,
display: Rc<dyn PlatformDisplay>,
raw: RawWindow,
x_window: x::Window,
callbacks: Mutex<Callbacks>,
inner: Mutex<LinuxWindowInner>,
callbacks: RefCell<Callbacks>,
inner: RefCell<LinuxWindowInner>,
}
#[derive(Clone)]
pub(crate) struct X11Window(pub(crate) Rc<X11WindowState>);
//todo!(linux): Remove other RawWindowHandle implementation
// todo(linux): Remove other RawWindowHandle implementation
unsafe impl blade_rwh::HasRawWindowHandle for RawWindow {
fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle {
let mut wh = blade_rwh::XcbWindowHandle::empty();
@@ -136,7 +139,7 @@ impl rwh::HasDisplayHandle for X11Window {
impl X11WindowState {
pub fn new(
options: WindowOptions,
xcb_connection: &Arc<xcb::Connection>,
xcb_connection: &Rc<xcb::Connection>,
x_main_screen_index: i32,
x_window: x::Window,
atoms: &XcbAtoms,
@@ -253,12 +256,12 @@ impl X11WindowState {
let gpu_extent = query_render_extent(xcb_connection, x_window);
Self {
xcb_connection: Arc::clone(xcb_connection),
xcb_connection: xcb_connection.clone(),
display: Rc::new(X11Display::new(xcb_connection, x_screen_index)),
raw,
x_window,
callbacks: Mutex::new(Callbacks::default()),
inner: Mutex::new(LinuxWindowInner {
callbacks: RefCell::new(Callbacks::default()),
inner: RefCell::new(LinuxWindowInner {
bounds,
scale_factor: 1.0,
renderer: BladeRenderer::new(gpu, gpu_extent),
@@ -268,23 +271,26 @@ impl X11WindowState {
}
pub fn destroy(&self) {
self.inner.lock().renderer.destroy();
self.inner.borrow_mut().renderer.destroy();
self.xcb_connection.send_request(&x::UnmapWindow {
window: self.x_window,
});
self.xcb_connection.send_request(&x::DestroyWindow {
window: self.x_window,
});
if let Some(fun) = self.callbacks.lock().close.take() {
if let Some(fun) = self.callbacks.borrow_mut().close.take() {
fun();
}
self.xcb_connection.flush().unwrap();
}
pub fn refresh(&self) {
let mut cb = self.callbacks.lock();
if let Some(ref mut fun) = cb.request_frame {
let mut cb = self.callbacks.borrow_mut();
if let Some(mut fun) = cb.request_frame.take() {
drop(cb);
fun();
let mut cb = self.callbacks.borrow_mut();
cb.request_frame = Some(fun);
}
}
@@ -292,10 +298,10 @@ impl X11WindowState {
let mut resize_args = None;
let do_move;
{
let mut inner = self.inner.lock();
let mut inner = self.inner.borrow_mut();
let old_bounds = mem::replace(&mut inner.bounds, bounds);
do_move = old_bounds.origin != bounds.origin;
//todo!(linux): use normal GPUI types here, refactor out the double
// todo(linux): use normal GPUI types here, refactor out the double
// viewport check and extra casts ( )
let gpu_size = query_render_extent(&self.xcb_connection, self.x_window);
if inner.renderer.viewport_size() != gpu_size {
@@ -306,7 +312,7 @@ impl X11WindowState {
}
}
let mut callbacks = self.callbacks.lock();
let mut callbacks = self.callbacks.borrow_mut();
if let Some((content_size, scale_factor)) = resize_args {
if let Some(ref mut fun) = callbacks.resize {
fun(content_size, scale_factor)
@@ -330,13 +336,13 @@ impl X11WindowState {
}
pub fn handle_input(&self, input: PlatformInput) {
if let Some(ref mut fun) = self.callbacks.lock().input {
if let Some(ref mut fun) = self.callbacks.borrow_mut().input {
if fun(input.clone()) {
return;
}
}
if let PlatformInput::KeyDown(event) = input {
let mut inner = self.inner.lock();
let mut inner = self.inner.borrow_mut();
if let Some(ref mut input_handler) = inner.input_handler {
if let Some(ime_key) = &event.keystroke.ime_key {
input_handler.replace_text_in_range(None, ime_key);
@@ -346,7 +352,7 @@ impl X11WindowState {
}
pub fn set_focused(&self, focus: bool) {
if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
if let Some(ref mut fun) = self.callbacks.borrow_mut().active_status_change {
fun(focus);
}
}
@@ -354,23 +360,29 @@ impl X11WindowState {
impl PlatformWindow for X11Window {
fn bounds(&self) -> WindowBounds {
WindowBounds::Fixed(self.0.inner.lock().bounds.map(|v| GlobalPixels(v as f32)))
WindowBounds::Fixed(
self.0
.inner
.borrow_mut()
.bounds
.map(|v| GlobalPixels(v as f32)),
)
}
fn content_size(&self) -> Size<Pixels> {
self.0.inner.lock().content_size()
self.0.inner.borrow_mut().content_size()
}
fn scale_factor(&self) -> f32 {
self.0.inner.lock().scale_factor
self.0.inner.borrow_mut().scale_factor
}
//todo!(linux)
// todo(linux)
fn titlebar_height(&self) -> Pixels {
unimplemented!()
}
//todo!(linux)
// todo(linux)
fn appearance(&self) -> WindowAppearance {
WindowAppearance::Light
}
@@ -390,7 +402,7 @@ impl PlatformWindow for X11Window {
)
}
//todo!(linux)
// todo(linux)
fn modifiers(&self) -> Modifiers {
Modifiers::default()
}
@@ -400,14 +412,14 @@ impl PlatformWindow for X11Window {
}
fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
self.0.inner.lock().input_handler = Some(input_handler);
self.0.inner.borrow_mut().input_handler = Some(input_handler);
}
fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
self.0.inner.lock().input_handler.take()
self.0.inner.borrow_mut().input_handler.take()
}
//todo!(linux)
// todo(linux)
fn prompt(
&self,
_level: PromptLevel,
@@ -435,10 +447,10 @@ impl PlatformWindow for X11Window {
});
}
//todo!(linux)
// todo(linux)
fn set_edited(&mut self, edited: bool) {}
//todo!(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
// todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS,
// but it looks like the equivalent for Linux is GTK specific:
//
// https://docs.gtk.org/gtk3/signal.Entry.insert-emoji.html
@@ -448,73 +460,69 @@ impl PlatformWindow for X11Window {
unimplemented!()
}
//todo!(linux)
// todo(linux)
fn minimize(&self) {
unimplemented!()
}
//todo!(linux)
// todo(linux)
fn zoom(&self) {
unimplemented!()
}
//todo!(linux)
// todo(linux)
fn toggle_full_screen(&self) {
unimplemented!()
}
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.lock().request_frame = Some(callback);
self.0.callbacks.borrow_mut().request_frame = Some(callback);
}
fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
self.0.callbacks.lock().input = Some(callback);
self.0.callbacks.borrow_mut().input = Some(callback);
}
fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.lock().active_status_change = Some(callback);
self.0.callbacks.borrow_mut().active_status_change = Some(callback);
}
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
self.0.callbacks.lock().resize = Some(callback);
self.0.callbacks.borrow_mut().resize = Some(callback);
}
fn on_fullscreen(&self, callback: Box<dyn FnMut(bool)>) {
self.0.callbacks.lock().fullscreen = Some(callback);
self.0.callbacks.borrow_mut().fullscreen = Some(callback);
}
fn on_moved(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.lock().moved = Some(callback);
self.0.callbacks.borrow_mut().moved = Some(callback);
}
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
self.0.callbacks.lock().should_close = Some(callback);
self.0.callbacks.borrow_mut().should_close = Some(callback);
}
fn on_close(&self, callback: Box<dyn FnOnce()>) {
self.0.callbacks.lock().close = Some(callback);
self.0.callbacks.borrow_mut().close = Some(callback);
}
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
self.0.callbacks.lock().appearance_changed = Some(callback);
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
}
//todo!(linux)
// todo(linux)
fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
unimplemented!()
}
fn draw(&self, scene: &Scene) {
let mut inner = self.0.inner.lock();
let mut inner = self.0.inner.borrow_mut();
inner.renderer.draw(scene);
}
fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
let inner = self.0.inner.lock();
let inner = self.0.inner.borrow_mut();
inner.renderer.sprite_atlas().clone()
}
fn set_graphics_profiler_enabled(&self, enabled: bool) {
unimplemented!("linux")
}
}

View File

@@ -293,6 +293,7 @@ impl MetalRenderer {
znear: 0.0,
zfar: 1.0,
});
for batch in scene.batches() {
let ok = match batch {
PrimitiveBatch::Shadows(shadows) => self.draw_shadows(

View File

@@ -1060,27 +1060,6 @@ impl PlatformWindow for MacWindow {
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.sprite_atlas().clone()
}
/// Enables or disables the Metal HUD for debugging purposes. Note that this only works
/// when the app is bundled and it has the `MetalHudEnabled` key set to true in Info.plist.
fn set_graphics_profiler_enabled(&self, enabled: bool) {
let this_lock = self.0.lock();
let layer = this_lock.renderer.layer();
unsafe {
if enabled {
let hud_properties = NSDictionary::dictionaryWithObject_forKey_(
nil,
ns_string("default"),
ns_string("mode"),
);
let _: () = msg_send![layer, setDeveloperHUDProperties: hud_properties];
} else {
let _: () =
msg_send![layer, setDeveloperHUDProperties: NSDictionary::dictionary(nil)];
}
}
}
}
impl HasWindowHandle for MacWindow {

View File

@@ -126,7 +126,7 @@ impl Platform for TestPlatform {
#[cfg(target_os = "macos")]
return Arc::new(crate::platform::mac::MacTextSystem::new());
// todo!("windows")
// todo("windows")
#[cfg(target_os = "windows")]
unimplemented!()
}

View File

@@ -7,7 +7,7 @@ use std::borrow::Cow;
pub(crate) struct TestTextSystem {}
//todo!(linux)
// todo(linux)
#[allow(unused)]
impl PlatformTextSystem for TestTextSystem {
fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {

View File

@@ -251,8 +251,6 @@ impl PlatformWindow for TestWindow {
self.0.lock().sprite_atlas.clone()
}
fn set_graphics_profiler_enabled(&self, _enabled: bool) {}
fn as_test(&mut self) -> Option<&mut TestWindow> {
Some(self)
}

View File

@@ -1,48 +1,21 @@
// todo!("windows"): remove
// todo("windows"): remove
#![cfg_attr(windows, allow(dead_code))]
use crate::{
point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, EntityId, Hsla, Pixels,
Point, ScaledPixels, StackingOrder,
bounds_tree::BoundsTree, point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges,
Hsla, Pixels, Point, ScaledPixels,
};
use collections::{BTreeMap, FxHashSet};
use std::{fmt::Debug, iter::Peekable, slice};
#[allow(non_camel_case_types, unused)]
pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
pub(crate) type LayerId = u32;
pub(crate) type DrawOrder = u32;
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[repr(C)]
pub(crate) struct ViewId {
low_bits: u32,
high_bits: u32,
}
impl From<EntityId> for ViewId {
fn from(value: EntityId) -> Self {
let value = value.as_u64();
Self {
low_bits: value as u32,
high_bits: (value >> 32) as u32,
}
}
}
impl From<ViewId> for EntityId {
fn from(value: ViewId) -> Self {
let value = (value.low_bits as u64) | ((value.high_bits as u64) << 32);
value.into()
}
}
#[derive(Default)]
pub(crate) struct Scene {
last_layer: Option<(StackingOrder, LayerId)>,
layers_by_order: BTreeMap<StackingOrder, LayerId>,
orders_by_layer: BTreeMap<LayerId, StackingOrder>,
pub(crate) primitives: Vec<Primitive>,
primitive_bounds: BoundsTree<ScaledPixels, ()>,
pub(crate) shadows: Vec<Shadow>,
pub(crate) quads: Vec<Quad>,
pub(crate) paths: Vec<Path<ScaledPixels>>,
@@ -54,12 +27,11 @@ pub(crate) struct Scene {
impl Scene {
pub fn clear(&mut self) {
self.last_layer = None;
self.layers_by_order.clear();
self.orders_by_layer.clear();
self.primitives.clear();
self.primitive_bounds.clear();
self.paths.clear();
self.shadows.clear();
self.quads.clear();
self.paths.clear();
self.underlines.clear();
self.monochrome_sprites.clear();
self.polychrome_sprites.clear();
@@ -70,6 +42,66 @@ impl Scene {
&self.paths
}
pub fn len(&self) -> usize {
self.primitives.len()
}
pub(crate) fn push(&mut self, primitive: impl Into<Primitive>) {
let mut primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.size.width <= ScaledPixels(0.)
|| clipped_bounds.size.height <= ScaledPixels(0.)
{
return;
}
let order = self.primitive_bounds.insert(clipped_bounds, ());
match &mut primitive {
Primitive::Shadow(shadow) => {
shadow.order = order;
self.shadows.push(shadow.clone());
}
Primitive::Quad(quad) => {
quad.order = order;
self.quads.push(quad.clone());
}
Primitive::Path(path) => {
path.order = order;
path.id = PathId(self.paths.len());
self.paths.push(path.clone());
}
Primitive::Underline(underline) => {
underline.order = order;
self.underlines.push(underline.clone());
}
Primitive::MonochromeSprite(sprite) => {
sprite.order = order;
self.monochrome_sprites.push(sprite.clone());
}
Primitive::PolychromeSprite(sprite) => {
sprite.order = order;
self.polychrome_sprites.push(sprite.clone());
}
Primitive::Surface(surface) => {
surface.order = order;
self.surfaces.push(surface.clone());
}
}
self.primitives.push(primitive);
}
pub fn finish(&mut self) {
self.shadows.sort_unstable();
self.quads.sort_unstable();
self.paths.sort_unstable();
self.underlines.sort_unstable();
self.monochrome_sprites.sort_unstable();
self.polychrome_sprites.sort_unstable();
self.surfaces.sort_unstable();
}
pub(crate) fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
BatchIterator {
shadows: &self.shadows,
@@ -95,162 +127,54 @@ impl Scene {
surfaces_iter: self.surfaces.iter().peekable(),
}
}
}
pub(crate) fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
let primitive = primitive.into();
let clipped_bounds = primitive
.bounds()
.intersect(&primitive.content_mask().bounds);
if clipped_bounds.size.width <= ScaledPixels(0.)
|| clipped_bounds.size.height <= ScaledPixels(0.)
{
return;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
pub(crate) enum PrimitiveKind {
Shadow,
#[default]
Quad,
Path,
Underline,
MonochromeSprite,
PolychromeSprite,
Surface,
}
let layer_id = self.layer_id_for_order(order);
match primitive {
Primitive::Shadow(mut shadow) => {
shadow.layer_id = layer_id;
self.shadows.push(shadow);
}
Primitive::Quad(mut quad) => {
quad.layer_id = layer_id;
self.quads.push(quad);
}
Primitive::Path(mut path) => {
path.layer_id = layer_id;
path.id = PathId(self.paths.len());
self.paths.push(path);
}
Primitive::Underline(mut underline) => {
underline.layer_id = layer_id;
self.underlines.push(underline);
}
Primitive::MonochromeSprite(mut sprite) => {
sprite.layer_id = layer_id;
self.monochrome_sprites.push(sprite);
}
Primitive::PolychromeSprite(mut sprite) => {
sprite.layer_id = layer_id;
self.polychrome_sprites.push(sprite);
}
Primitive::Surface(mut surface) => {
surface.layer_id = layer_id;
self.surfaces.push(surface);
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
pub(crate) enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
Surface(Surface),
}
impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.bounds,
Primitive::Quad(quad) => &quad.bounds,
Primitive::Path(path) => &path.bounds,
Primitive::Underline(underline) => &underline.bounds,
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
Primitive::Surface(surface) => &surface.bounds,
}
}
fn layer_id_for_order(&mut self, order: &StackingOrder) -> LayerId {
if let Some((last_order, last_layer_id)) = self.last_layer.as_ref() {
if order == last_order {
return *last_layer_id;
}
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.content_mask,
Primitive::Quad(quad) => &quad.content_mask,
Primitive::Path(path) => &path.content_mask,
Primitive::Underline(underline) => &underline.content_mask,
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
Primitive::Surface(surface) => &surface.content_mask,
}
let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
*layer_id
} else {
let next_id = self.layers_by_order.len() as LayerId;
self.layers_by_order.insert(order.clone(), next_id);
self.orders_by_layer.insert(next_id, order.clone());
next_id
};
self.last_layer = Some((order.clone(), layer_id));
layer_id
}
pub fn reuse_views(&mut self, views: &FxHashSet<EntityId>, prev_scene: &mut Self) {
for shadow in prev_scene.shadows.drain(..) {
if views.contains(&shadow.view_id.into()) {
let order = &prev_scene.orders_by_layer[&shadow.layer_id];
self.insert(order, shadow);
}
}
for quad in prev_scene.quads.drain(..) {
if views.contains(&quad.view_id.into()) {
let order = &prev_scene.orders_by_layer[&quad.layer_id];
self.insert(order, quad);
}
}
for path in prev_scene.paths.drain(..) {
if views.contains(&path.view_id.into()) {
let order = &prev_scene.orders_by_layer[&path.layer_id];
self.insert(order, path);
}
}
for underline in prev_scene.underlines.drain(..) {
if views.contains(&underline.view_id.into()) {
let order = &prev_scene.orders_by_layer[&underline.layer_id];
self.insert(order, underline);
}
}
for sprite in prev_scene.monochrome_sprites.drain(..) {
if views.contains(&sprite.view_id.into()) {
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
self.insert(order, sprite);
}
}
for sprite in prev_scene.polychrome_sprites.drain(..) {
if views.contains(&sprite.view_id.into()) {
let order = &prev_scene.orders_by_layer[&sprite.layer_id];
self.insert(order, sprite);
}
}
for surface in prev_scene.surfaces.drain(..) {
if views.contains(&surface.view_id.into()) {
let order = &prev_scene.orders_by_layer[&surface.layer_id];
self.insert(order, surface);
}
}
}
pub fn finish(&mut self) {
let mut orders = vec![0; self.layers_by_order.len()];
for (ix, layer_id) in self.layers_by_order.values().enumerate() {
orders[*layer_id as usize] = ix as u32;
}
for shadow in &mut self.shadows {
shadow.order = orders[shadow.layer_id as usize];
}
self.shadows.sort_by_key(|shadow| shadow.order);
for quad in &mut self.quads {
quad.order = orders[quad.layer_id as usize];
}
self.quads.sort_by_key(|quad| quad.order);
for path in &mut self.paths {
path.order = orders[path.layer_id as usize];
}
self.paths.sort_by_key(|path| path.order);
for underline in &mut self.underlines {
underline.order = orders[underline.layer_id as usize];
}
self.underlines.sort_by_key(|underline| underline.order);
for monochrome_sprite in &mut self.monochrome_sprites {
monochrome_sprite.order = orders[monochrome_sprite.layer_id as usize];
}
self.monochrome_sprites.sort_by_key(|sprite| sprite.order);
for polychrome_sprite in &mut self.polychrome_sprites {
polychrome_sprite.order = orders[polychrome_sprite.layer_id as usize];
}
self.polychrome_sprites.sort_by_key(|sprite| sprite.order);
for surface in &mut self.surfaces {
surface.order = orders[surface.layer_id as usize];
}
self.surfaces.sort_by_key(|surface| surface.order);
}
}
@@ -439,54 +363,6 @@ impl<'a> Iterator for BatchIterator<'a> {
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Default)]
pub(crate) enum PrimitiveKind {
Shadow,
#[default]
Quad,
Path,
Underline,
MonochromeSprite,
PolychromeSprite,
Surface,
}
pub(crate) enum Primitive {
Shadow(Shadow),
Quad(Quad),
Path(Path<ScaledPixels>),
Underline(Underline),
MonochromeSprite(MonochromeSprite),
PolychromeSprite(PolychromeSprite),
Surface(Surface),
}
impl Primitive {
pub fn bounds(&self) -> &Bounds<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.bounds,
Primitive::Quad(quad) => &quad.bounds,
Primitive::Path(path) => &path.bounds,
Primitive::Underline(underline) => &underline.bounds,
Primitive::MonochromeSprite(sprite) => &sprite.bounds,
Primitive::PolychromeSprite(sprite) => &sprite.bounds,
Primitive::Surface(surface) => &surface.bounds,
}
}
pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
match self {
Primitive::Shadow(shadow) => &shadow.content_mask,
Primitive::Quad(quad) => &quad.content_mask,
Primitive::Path(path) => &path.content_mask,
Primitive::Underline(underline) => &underline.content_mask,
Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
Primitive::Surface(surface) => &surface.content_mask,
}
}
}
#[derive(Debug)]
pub(crate) enum PrimitiveBatch<'a> {
Shadows(&'a [Shadow]),
@@ -507,8 +383,6 @@ pub(crate) enum PrimitiveBatch<'a> {
#[derive(Default, Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Quad {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@@ -539,8 +413,6 @@ impl From<Quad> for Primitive {
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Underline {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@@ -570,8 +442,6 @@ impl From<Underline> for Primitive {
#[derive(Debug, Clone, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct Shadow {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub corner_radii: Corners<ScaledPixels>,
@@ -602,8 +472,6 @@ impl From<Shadow> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct MonochromeSprite {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@@ -635,8 +503,6 @@ impl From<MonochromeSprite> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub(crate) struct PolychromeSprite {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@@ -669,8 +535,6 @@ impl From<PolychromeSprite> for Primitive {
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Surface {
pub view_id: ViewId,
pub layer_id: LayerId,
pub order: DrawOrder,
pub bounds: Bounds<ScaledPixels>,
pub content_mask: ContentMask<ScaledPixels>,
@@ -700,11 +564,9 @@ impl From<Surface> for Primitive {
pub(crate) struct PathId(pub(crate) usize);
/// A line made up of a series of vertices and control points.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Path<P: Clone + Default + Debug> {
pub(crate) id: PathId,
pub(crate) view_id: ViewId,
layer_id: LayerId,
order: DrawOrder,
pub(crate) bounds: Bounds<P>,
pub(crate) content_mask: ContentMask<P>,
@@ -720,8 +582,6 @@ impl Path<Pixels> {
pub fn new(start: Point<Pixels>) -> Self {
Self {
id: PathId(0),
view_id: ViewId::default(),
layer_id: LayerId::default(),
order: DrawOrder::default(),
vertices: Vec::new(),
start,
@@ -740,8 +600,6 @@ impl Path<Pixels> {
pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
Path {
id: self.id,
view_id: self.view_id,
layer_id: self.layer_id,
order: self.order,
bounds: self.bounds.scale(factor),
content_mask: self.content_mask.scale(factor),

View File

@@ -115,9 +115,6 @@ pub struct Style {
/// The mouse cursor style shown when the mouse pointer is over an element.
pub mouse_cursor: Option<CursorStyle>,
/// The z-index to set for this element
pub z_index: Option<u16>,
/// Whether to draw a red debugging outline around this element
#[cfg(debug_assertions)]
pub debug: bool,
@@ -208,7 +205,7 @@ impl Default for TextStyle {
fn default() -> Self {
TextStyle {
color: black(),
// todo!(linux) make this configurable or choose better default
// todo(linux) make this configurable or choose better default
font_family: if cfg!(target_os = "linux") {
"FreeMono".into()
} else {
@@ -323,6 +320,13 @@ pub struct HighlightStyle {
impl Eq for HighlightStyle {}
impl Style {
/// Returns true if the style is visible and the background is opaque.
pub fn has_opaque_background(&self) -> bool {
self.background
.as_ref()
.is_some_and(|fill| fill.color().is_some_and(|color| !color.is_transparent()))
}
/// Get the text style in this element style.
pub fn text_style(&self) -> Option<&TextStyleRefinement> {
if self.text.is_some() {
@@ -402,97 +406,87 @@ impl Style {
let rem_size = cx.rem_size();
cx.with_z_index(0, |cx| {
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
});
cx.paint_shadows(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
&self.box_shadow,
);
let background_color = self.background.as_ref().and_then(Fill::color);
if background_color.map_or(false, |color| !color.is_transparent()) {
cx.with_z_index(1, |cx| {
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
});
let mut border_color = background_color.unwrap_or_default();
border_color.a = 0.;
cx.paint_quad(quad(
bounds,
self.corner_radii.to_pixels(bounds.size, rem_size),
background_color.unwrap_or_default(),
Edges::default(),
border_color,
));
}
cx.with_z_index(2, |cx| {
continuation(cx);
});
continuation(cx);
if self.is_border_visible() {
cx.with_z_index(3, |cx| {
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let corner_radii = self.corner_radii.to_pixels(bounds.size, rem_size);
let border_widths = self.border_widths.to_pixels(rem_size);
let max_border_width = border_widths.max();
let max_corner_radius = corner_radii.max();
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right()
+ point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left()
- point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let top_bounds = Bounds::from_corners(
bounds.origin,
bounds.upper_right() + point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
);
let bottom_bounds = Bounds::from_corners(
bounds.lower_left() - point(Pixels::ZERO, max_border_width.max(max_corner_radius)),
bounds.lower_right(),
);
let left_bounds = Bounds::from_corners(
top_bounds.lower_left(),
bottom_bounds.origin + point(max_border_width, Pixels::ZERO),
);
let right_bounds = Bounds::from_corners(
top_bounds.lower_right() - point(max_border_width, Pixels::ZERO),
bottom_bounds.upper_right(),
);
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
let mut background = self.border_color.unwrap_or_default();
background.a = 0.;
let quad = quad(
bounds,
corner_radii,
background,
border_widths,
self.border_color.unwrap_or_default(),
);
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
cx.with_content_mask(Some(ContentMask { bounds: top_bounds }), |cx| {
cx.paint_quad(quad.clone());
});
cx.with_content_mask(
Some(ContentMask {
bounds: right_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: bottom_bounds,
}),
|cx| {
cx.paint_quad(quad.clone());
},
);
cx.with_content_mask(
Some(ContentMask {
bounds: left_bounds,
}),
|cx| {
cx.paint_quad(quad);
},
);
}
#[cfg(debug_assertions)]
@@ -545,7 +539,6 @@ impl Default for Style {
box_shadow: Default::default(),
text: TextStyleRefinement::default(),
mouse_cursor: None,
z_index: None,
#[cfg(debug_assertions)]
debug: false,

View File

@@ -15,12 +15,6 @@ pub trait Styled: Sized {
gpui_macros::style_helpers!();
/// Set the z-index of this element.
fn z_index(mut self, z_index: u16) -> Self {
self.style().z_index = Some(z_index);
self
}
/// Sets the position of the element to `relative`.
/// [Docs](https://tailwindcss.com/docs/position)
fn relative(mut self) -> Self {

View File

@@ -51,7 +51,7 @@ impl TaffyLayoutEngine {
self.styles.get(&layout_id)
}
pub fn request_layout(
pub fn before_layout(
&mut self,
style: &Style,
rem_size: Pixels,
@@ -447,6 +447,27 @@ pub enum AvailableSpace {
MaxContent,
}
impl AvailableSpace {
/// Returns a `Size` with both width and height set to `AvailableSpace::MinContent`.
///
/// This function is useful when you want to create a `Size` with the minimum content constraints
/// for both dimensions.
///
/// # Examples
///
/// ```
/// let min_content_size = AvailableSpace::min_size();
/// assert_eq!(min_content_size.width, AvailableSpace::MinContent);
/// assert_eq!(min_content_size.height, AvailableSpace::MinContent);
/// ```
pub const fn min_size() -> Size<Self> {
Size {
width: Self::MinContent,
height: Self::MinContent,
}
}
}
impl From<AvailableSpace> for TaffyAvailableSpace {
fn from(space: AvailableSpace) -> TaffyAvailableSpace {
match space {

View File

@@ -291,8 +291,8 @@ impl WindowTextSystem {
}
}
pub(crate) fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
self.line_layout_cache.with_view(view_id, f)
pub(crate) fn set_parent_view_id(&self, view_id: Option<EntityId>) {
self.line_layout_cache.set_parent_view_id(view_id);
}
/// Shape the given line, at the given font_size, for painting to the screen.

View File

@@ -118,11 +118,13 @@ fn paint_line(
let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
let text_system = cx.text_system().clone();
let mut glyph_origin = origin;
let mut prev_glyph_position = Point::default();
for (run_ix, run) in layout.runs.iter().enumerate() {
let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
let max_glyph_size = cx
.text_system()
.bounding_box(run.font_id, layout.font_size)
.size;
for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
glyph_origin.x += glyph.position.x - prev_glyph_position.x;

View File

@@ -4,6 +4,7 @@ use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::SmallVec;
use std::{
borrow::Borrow,
cell::Cell,
hash::{Hash, Hasher},
sync::Arc,
};
@@ -276,7 +277,7 @@ impl WrappedLineLayout {
}
pub(crate) struct LineLayoutCache {
view_stack: Mutex<Vec<EntityId>>,
parent_view_id: Cell<Option<EntityId>>,
previous_frame: Mutex<FxHashMap<CacheKey, Arc<LineLayout>>>,
current_frame: RwLock<FxHashMap<CacheKey, Arc<LineLayout>>>,
previous_frame_wrapped: Mutex<FxHashMap<CacheKey, Arc<WrappedLineLayout>>>,
@@ -287,7 +288,7 @@ pub(crate) struct LineLayoutCache {
impl LineLayoutCache {
pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
Self {
view_stack: Mutex::default(),
parent_view_id: Cell::default(),
previous_frame: Mutex::default(),
current_frame: RwLock::default(),
previous_frame_wrapped: Mutex::default(),
@@ -297,8 +298,6 @@ impl LineLayoutCache {
}
pub fn finish_frame(&self, reused_views: &FxHashSet<EntityId>) {
debug_assert_eq!(self.view_stack.lock().len(), 0);
let mut prev_frame = self.previous_frame.lock();
let mut curr_frame = self.current_frame.write();
for (key, layout) in prev_frame.drain() {
@@ -324,15 +323,8 @@ impl LineLayoutCache {
std::mem::swap(&mut *prev_frame_wrapped, &mut *curr_frame_wrapped);
}
pub fn with_view<R>(&self, view_id: EntityId, f: impl FnOnce() -> R) -> R {
self.view_stack.lock().push(view_id);
let result = f();
self.view_stack.lock().pop();
result
}
fn parent_view_id(&self) -> Option<EntityId> {
self.view_stack.lock().last().copied()
pub fn set_parent_view_id(&self, view_id: Option<EntityId>) {
self.parent_view_id.replace(view_id);
}
pub fn layout_wrapped_line(
@@ -347,7 +339,7 @@ impl LineLayoutCache {
font_size,
runs,
wrap_width,
parent_view_id: self.parent_view_id(),
parent_view_id: self.parent_view_id.get(),
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame_wrapped.upgradable_read();
@@ -376,7 +368,7 @@ impl LineLayoutCache {
font_size,
runs: SmallVec::from(runs),
wrap_width,
parent_view_id: self.parent_view_id(),
parent_view_id: self.parent_view_id.get(),
};
current_frame.insert(key, layout.clone());
layout
@@ -389,7 +381,7 @@ impl LineLayoutCache {
font_size,
runs,
wrap_width: None,
parent_view_id: self.parent_view_id(),
parent_view_id: self.parent_view_id.get(),
} as &dyn AsCacheKeyRef;
let current_frame = self.current_frame.upgradable_read();
@@ -408,7 +400,7 @@ impl LineLayoutCache {
font_size,
runs: SmallVec::from(runs),
wrap_width: None,
parent_view_id: self.parent_view_id(),
parent_view_id: self.parent_view_id.get(),
};
current_frame.insert(key, layout.clone());
layout

View File

@@ -1,14 +1,15 @@
use crate::{
seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, AvailableSpace, Bounds,
seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds,
ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
FocusableView, IntoElement, LayoutId, Model, Pixels, Point, Render, Size, StackingOrder, Style,
TextStyle, ViewContext, VisualContext, WeakModel,
FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style, TextStyle,
ViewContext, VisualContext, WeakModel,
};
use anyhow::{Context, Result};
use std::{
any::{type_name, TypeId},
fmt,
hash::{Hash, Hasher},
ops::Range,
};
/// A view is a piece of state that can be presented on screen by implementing the [Render] trait.
@@ -20,17 +21,16 @@ pub struct View<V> {
impl<V> Sealed for View<V> {}
#[doc(hidden)]
pub struct AnyViewState {
struct AnyViewState {
root_style: Style,
next_stacking_order_id: u16,
cache_key: Option<ViewCacheKey>,
element: Option<AnyElement>,
after_layout_range: Range<AfterLayoutIndex>,
paint_range: Range<PaintIndex>,
cache_key: ViewCacheKey,
}
#[derive(Default)]
struct ViewCacheKey {
bounds: Bounds<Pixels>,
stacking_order: StackingOrder,
content_mask: ContentMask<Pixels>,
text_style: TextStyle,
}
@@ -90,22 +90,39 @@ impl<V: 'static> View<V> {
}
impl<V: Render> Element for View<V> {
type State = Option<AnyElement>;
type BeforeLayout = AnyElement;
type AfterLayout = ();
fn request_layout(
&mut self,
_state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
cx.with_view_id(self.entity_id(), |cx| {
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
let layout_id = element.request_layout(cx);
(layout_id, Some(element))
let layout_id = element.before_layout(cx);
(layout_id, element)
})
}
fn paint(&mut self, _: Bounds<Pixels>, element: &mut Self::State, cx: &mut ElementContext) {
cx.paint_view(self.entity_id(), |cx| element.take().unwrap().paint(cx));
fn after_layout(
&mut self,
_: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) {
cx.set_view_id(self.entity_id());
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.after_layout(cx)
})
}
fn paint(
&mut self,
_: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
_: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.paint(cx)
})
}
}
@@ -203,7 +220,7 @@ impl<V> Eq for WeakView<V> {}
#[derive(Clone, Debug)]
pub struct AnyView {
model: AnyModel,
request_layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
cache: bool,
}
@@ -220,7 +237,7 @@ impl AnyView {
pub fn downgrade(&self) -> AnyWeakView {
AnyWeakView {
model: self.model.downgrade(),
layout: self.request_layout,
render: self.render,
}
}
@@ -231,7 +248,7 @@ impl AnyView {
Ok(model) => Ok(View { model }),
Err(model) => Err(Self {
model,
request_layout: self.request_layout,
render: self.render,
cache: self.cache,
}),
}
@@ -246,113 +263,154 @@ impl AnyView {
pub fn entity_id(&self) -> EntityId {
self.model.entity_id()
}
pub(crate) fn draw(
&self,
origin: Point<Pixels>,
available_space: Size<AvailableSpace>,
cx: &mut ElementContext,
) {
cx.paint_view(self.entity_id(), |cx| {
cx.with_absolute_element_offset(origin, |cx| {
let (layout_id, mut rendered_element) = (self.request_layout)(self, cx);
cx.compute_layout(layout_id, available_space);
rendered_element.paint(cx)
});
})
}
}
impl<V: Render> From<View<V>> for AnyView {
fn from(value: View<V>) -> Self {
AnyView {
model: value.model.into_any(),
request_layout: any_view::request_layout::<V>,
render: any_view::render::<V>,
cache: false,
}
}
}
impl Element for AnyView {
type State = AnyViewState;
type BeforeLayout = Option<AnyElement>;
type AfterLayout = Option<AnyElement>;
fn request_layout(
&mut self,
state: Option<Self::State>,
cx: &mut ElementContext,
) -> (LayoutId, Self::State) {
cx.with_view_id(self.entity_id(), |cx| {
if self.cache
&& !cx.window.dirty_views.contains(&self.entity_id())
&& !cx.window.refreshing
{
if let Some(state) = state {
let layout_id = cx.request_layout(&state.root_style, None);
return (layout_id, state);
}
}
fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
if self.cache {
cx.with_element_state::<AnyViewState, _>(
Some(ElementId::View(self.entity_id())),
|element_state, cx| {
let mut element_state = element_state.unwrap();
let (layout_id, element) = (self.request_layout)(self, cx);
let root_style = cx.layout_style(layout_id).unwrap().clone();
let state = AnyViewState {
root_style,
next_stacking_order_id: 0,
cache_key: None,
element: Some(element),
};
(layout_id, state)
})
if !cx.window.dirty_views.contains(&self.entity_id()) && !cx.window.refreshing {
if let Some(root_style) = element_state
.as_ref()
.map(|element_state| &element_state.root_style)
{
let layout_id = cx.request_layout(root_style, None);
return ((layout_id, None), element_state);
}
}
let mut element = (self.render)(self, cx);
let layout_id = element.before_layout(cx);
let element_state = Some(AnyViewState {
root_style: cx.layout_style(layout_id).unwrap().clone(),
cache_key: ViewCacheKey::default(),
after_layout_range: AfterLayoutIndex::default()
..AfterLayoutIndex::default(),
paint_range: PaintIndex::default()..PaintIndex::default(),
});
((layout_id, Some(element)), element_state)
},
)
} else {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
let mut element = (self.render)(self, cx);
let layout_id = element.before_layout(cx);
(layout_id, Some(element))
})
}
}
fn paint(&mut self, bounds: Bounds<Pixels>, state: &mut Self::State, cx: &mut ElementContext) {
cx.paint_view(self.entity_id(), |cx| {
if !self.cache {
state.element.take().unwrap().paint(cx);
return;
}
fn after_layout(
&mut self,
bounds: Bounds<Pixels>,
element: &mut Self::BeforeLayout,
cx: &mut ElementContext,
) -> Option<AnyElement> {
cx.set_view_id(self.entity_id());
if self.cache {
cx.with_element_state::<AnyViewState, _>(
Some(ElementId::View(self.entity_id())),
|element_state, cx| {
let mut element_state = element_state.unwrap().unwrap();
if let Some(cache_key) = state.cache_key.as_mut() {
if cache_key.bounds == bounds
&& cache_key.content_mask == cx.content_mask()
&& cache_key.stacking_order == *cx.stacking_order()
&& cache_key.text_style == cx.text_style()
{
cx.reuse_view(state.next_stacking_order_id);
return;
}
}
let after_layout_start = cx.window.next_frame.after_layout_index();
let content_mask = cx.content_mask();
let text_style = cx.text_style();
if let Some(mut element) = state.element.take() {
element.paint(cx);
} else {
let mut element = (self.request_layout)(self, cx).1;
element.draw(bounds.origin, bounds.size.into(), cx);
}
let element = if let Some(mut element) = element.take() {
element.after_layout(cx);
Some(element)
} else if element_state.cache_key.bounds == bounds
&& element_state.cache_key.content_mask == content_mask
&& element_state.cache_key.text_style == text_style
{
cx.reuse_after_layout(element_state.after_layout_range.clone());
None
} else {
let mut element = (self.render)(self, cx);
let layout_id = element.before_layout(cx);
cx.compute_layout(layout_id, bounds.size.into());
element_state.root_style = cx.layout_style(layout_id).unwrap().clone();
cx.with_absolute_element_offset(bounds.origin, |cx| {
element.after_layout(cx)
});
state.next_stacking_order_id = cx
.window
.next_frame
.next_stacking_order_ids
.last()
.copied()
.unwrap();
state.cache_key = Some(ViewCacheKey {
bounds,
stacking_order: cx.stacking_order().clone(),
content_mask: cx.content_mask(),
text_style: cx.text_style(),
});
})
Some(element)
};
let after_layout_end = cx.window.next_frame.after_layout_index();
element_state.after_layout_range = after_layout_start..after_layout_end;
element_state.cache_key.bounds = bounds;
element_state.cache_key.content_mask = content_mask;
element_state.cache_key.text_style = text_style;
(element, Some(element_state))
},
)
} else {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
let mut element = element.take().unwrap();
element.after_layout(cx);
Some(element)
})
}
}
fn paint(
&mut self,
_bounds: Bounds<Pixels>,
_: &mut Self::BeforeLayout,
element: &mut Self::AfterLayout,
cx: &mut ElementContext,
) {
if self.cache {
cx.with_element_state::<AnyViewState, _>(
Some(ElementId::View(self.entity_id())),
|element_state, cx| {
let mut element_state = element_state.unwrap().unwrap();
let paint_start = cx.window.next_frame.paint_index();
if let Some(element) = element {
element.paint(cx);
} else {
cx.reuse_paint(element_state.paint_range.clone());
}
let paint_end = cx.window.next_frame.paint_index();
element_state.paint_range = paint_start..paint_end;
((), Some(element_state))
},
)
} else {
cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
element.as_mut().unwrap().paint(cx);
})
}
}
}
impl<V: 'static + Render> IntoElement for View<V> {
type Element = View<V>;
fn element_id(&self) -> Option<ElementId> {
Some(ElementId::from_entity_id(self.model.entity_id))
}
fn into_element(self) -> Self::Element {
self
}
@@ -361,10 +419,6 @@ impl<V: 'static + Render> IntoElement for View<V> {
impl IntoElement for AnyView {
type Element = Self;
fn element_id(&self) -> Option<ElementId> {
Some(ElementId::from_entity_id(self.model.entity_id))
}
fn into_element(self) -> Self::Element {
self
}
@@ -373,7 +427,7 @@ impl IntoElement for AnyView {
/// A weak, dynamically-typed view handle that does not prevent the view from being released.
pub struct AnyWeakView {
model: AnyWeakModel,
layout: fn(&AnyView, &mut ElementContext) -> (LayoutId, AnyElement),
render: fn(&AnyView, &mut ElementContext) -> AnyElement,
}
impl AnyWeakView {
@@ -382,7 +436,7 @@ impl AnyWeakView {
let model = self.model.upgrade()?;
Some(AnyView {
model,
request_layout: self.layout,
render: self.render,
cache: false,
})
}
@@ -392,7 +446,7 @@ impl<V: 'static + Render> From<WeakView<V>> for AnyWeakView {
fn from(view: WeakView<V>) -> Self {
Self {
model: view.model.into(),
layout: any_view::request_layout::<V>,
render: any_view::render::<V>,
}
}
}
@@ -412,15 +466,13 @@ impl std::fmt::Debug for AnyWeakView {
}
mod any_view {
use crate::{AnyElement, AnyView, ElementContext, IntoElement, LayoutId, Render};
use crate::{AnyElement, AnyView, ElementContext, IntoElement, Render};
pub(crate) fn request_layout<V: 'static + Render>(
pub(crate) fn render<V: 'static + Render>(
view: &AnyView,
cx: &mut ElementContext,
) -> (LayoutId, AnyElement) {
) -> AnyElement {
let view = view.clone().downcast::<V>().unwrap();
let mut element = view.update(cx, |view, cx| view.render(cx).into_any_element());
let layout_id = element.request_layout(cx);
(layout_id, element)
view.update(cx, |view, cx| view.render(cx).into_any_element())
}
}

View File

@@ -1,13 +1,12 @@
use crate::{
px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext,
AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId,
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult,
Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent,
MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point,
PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription,
TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowAppearance, WindowBounds,
WindowOptions, WindowTextSystem,
px, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, Bounds,
Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId,
Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, Global, GlobalElementId,
Hsla, KeyBinding, KeyDownEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent, Model,
ModelContext, Modifiers, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas,
PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels,
SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, View, VisualContext,
WeakView, WindowAppearance, WindowBounds, WindowOptions, WindowTextSystem,
};
use anyhow::{anyhow, Context as _, Result};
use collections::FxHashSet;
@@ -37,8 +36,6 @@ use util::{measure, ResultExt};
mod element_cx;
pub use element_cx::*;
const ACTIVE_DRAG_Z_INDEX: u16 = 1;
/// A global stacking order, which is created by stacking successive z-index values.
/// Each z-index will always be interpreted in the context of its parent z-index.
#[derive(Debug, Deref, DerefMut, Clone, Ord, PartialOrd, PartialEq, Eq, Default)]
@@ -257,6 +254,7 @@ pub struct Window {
pub(crate) element_id_stack: GlobalElementId,
pub(crate) rendered_frame: Frame,
pub(crate) next_frame: Frame,
pub(crate) next_hitbox_id: HitboxId,
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
pub(crate) dirty_views: FxHashSet<EntityId>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
@@ -264,6 +262,7 @@ pub struct Window {
focus_lost_listeners: SubscriberSet<(), AnyObserver>,
default_prevented: bool,
mouse_position: Point<Pixels>,
mouse_hit_test: HitTest,
modifiers: Modifiers,
scale_factor: f32,
bounds: WindowBounds,
@@ -280,7 +279,6 @@ pub struct Window {
pub(crate) focus: Option<FocusId>,
focus_enabled: bool,
pending_input: Option<PendingInput>,
graphics_profiler_enabled: bool,
}
#[derive(Default, Debug)]
@@ -452,12 +450,14 @@ impl Window {
rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
next_frame_callbacks,
next_hitbox_id: HitboxId::default(),
dirty_views: FxHashSet::default(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(),
focus_lost_listeners: SubscriberSet::new(),
default_prevented: true,
mouse_position,
mouse_hit_test: HitTest::default(),
modifiers,
scale_factor,
bounds,
@@ -474,7 +474,6 @@ impl Window {
focus: None,
focus_enabled: true,
pending_input: None,
graphics_profiler_enabled: false,
}
}
fn new_focus_listener(
@@ -585,7 +584,7 @@ impl<'a> WindowContext<'a> {
}
/// Accessor for the text system.
pub fn text_system(&self) -> &Arc<WindowTextSystem> {
pub fn text_system(&self) -> &WindowTextSystem {
&self.window.text_system
}
@@ -859,95 +858,6 @@ impl<'a> WindowContext<'a> {
self.window.modifiers
}
/// Returns true if there is no opaque layer containing the given point
/// on top of the given level. Layers who are extensions of the queried layer
/// are not considered to be on top of queried layer.
pub fn was_top_layer(&self, point: &Point<Pixels>, layer: &StackingOrder) -> bool {
// Precondition: the depth map is ordered from topmost to bottomost.
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
if layer >= opaque_layer {
// The queried layer is either above or is the same as the this opaque layer.
// Anything after this point is guaranteed to be below the queried layer.
return true;
}
if !bounds.contains(point) {
// This opaque layer is above the queried layer but it doesn't contain
// the given position, so we can ignore it even if it's above.
continue;
}
// At this point, we've established that this opaque layer is on top of the queried layer
// and contains the position:
// If neither the opaque layer or the queried layer is an extension of the other then
// we know they are on different stacking orders, and return false.
let is_on_same_layer = opaque_layer
.iter()
.zip(layer.iter())
.all(|(a, b)| a.z_index == b.z_index);
if !is_on_same_layer {
return false;
}
}
true
}
pub(crate) fn was_top_layer_under_active_drag(
&self,
point: &Point<Pixels>,
layer: &StackingOrder,
) -> bool {
// Precondition: the depth map is ordered from topmost to bottomost.
for (opaque_layer, _, bounds) in self.window.rendered_frame.depth_map.iter() {
if layer >= opaque_layer {
// The queried layer is either above or is the same as the this opaque layer.
// Anything after this point is guaranteed to be below the queried layer.
return true;
}
if !bounds.contains(point) {
// This opaque layer is above the queried layer but it doesn't contain
// the given position, so we can ignore it even if it's above.
continue;
}
// All normal content is rendered with a base z-index of 0, we know that if the root of this opaque layer
// equals `ACTIVE_DRAG_Z_INDEX` then it must be the drag layer and we can ignore it as we are
// looking to see if the queried layer was the topmost underneath the drag layer.
if opaque_layer
.first()
.map(|c| c.z_index == ACTIVE_DRAG_Z_INDEX)
.unwrap_or(false)
{
continue;
}
// At this point, we've established that this opaque layer is on top of the queried layer
// and contains the position:
// If neither the opaque layer or the queried layer is an extension of the other then
// we know they are on different stacking orders, and return false.
let is_on_same_layer = opaque_layer
.iter()
.zip(layer.iter())
.all(|(a, b)| a.z_index == b.z_index);
if !is_on_same_layer {
return false;
}
}
true
}
/// Called during painting to get the current stacking order.
pub fn stacking_order(&self) -> &StackingOrder {
&self.window.next_frame.z_index_stack
}
/// Produces a new frame and assigns it to `rendered_frame`. To actually show
/// the contents of the new [Scene], use [present].
#[profiling::function]
@@ -961,54 +871,7 @@ impl<'a> WindowContext<'a> {
requested_handler.handler = input_handler;
}
let root_view = self.window.root_view.take().unwrap();
self.with_element_context(|cx| {
cx.with_z_index(0, |cx| {
cx.with_key_dispatch(Some(KeyContext::default()), None, |_, cx| {
// We need to use cx.cx here so we can utilize borrow splitting
for (action_type, action_listeners) in &cx.cx.app.global_action_listeners {
for action_listener in action_listeners.iter().cloned() {
cx.cx.window.next_frame.dispatch_tree.on_action(
*action_type,
Rc::new(
move |action: &dyn Any, phase, cx: &mut WindowContext<'_>| {
action_listener(action, phase, cx)
},
),
)
}
}
let available_space = cx.window.viewport_size.map(Into::into);
root_view.draw(Point::default(), available_space, cx);
})
})
});
if let Some(active_drag) = self.app.active_drag.take() {
self.with_element_context(|cx| {
cx.with_z_index(ACTIVE_DRAG_Z_INDEX, |cx| {
let offset = cx.mouse_position() - active_drag.cursor_offset;
let available_space =
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
active_drag.view.draw(offset, available_space, cx);
})
});
self.active_drag = Some(active_drag);
} else if let Some(tooltip_request) = self.window.next_frame.tooltip_request.take() {
self.with_element_context(|cx| {
cx.with_z_index(1, |cx| {
let available_space =
size(AvailableSpace::MinContent, AvailableSpace::MinContent);
tooltip_request.tooltip.view.draw(
tooltip_request.tooltip.cursor_offset,
available_space,
cx,
);
})
});
self.window.next_frame.tooltip_request = Some(tooltip_request);
}
self.with_element_context(|cx| cx.draw_roots());
self.window.dirty_views.clear();
self.window
@@ -1020,16 +883,10 @@ impl<'a> WindowContext<'a> {
);
self.window.next_frame.focus = self.window.focus;
self.window.next_frame.window_active = self.window.active.get();
self.window.root_view = Some(root_view);
// Set the cursor only if we're the active window.
let cursor_style = self
.window
.next_frame
.requested_cursor_style
.take()
.unwrap_or(CursorStyle::Arrow);
if self.is_window_active() {
let cursor_style = self.compute_cursor_style().unwrap_or(CursorStyle::Arrow);
self.platform.set_cursor_style(cursor_style);
}
@@ -1102,6 +959,18 @@ impl<'a> WindowContext<'a> {
profiling::finish_frame!();
}
fn compute_cursor_style(&mut self) -> Option<CursorStyle> {
// TODO: maybe we should have a HashMap keyed by HitboxId.
let request = self
.window
.next_frame
.cursor_styles
.iter()
.rev()
.find(|request| request.hitbox_id.is_hovered(self))?;
Some(request.style)
}
/// Dispatch a given keystroke as though the user had typed it.
/// You can create a keystroke with Keystroke::parse("").
pub fn dispatch_keystroke(&mut self, keystroke: Keystroke) -> bool {
@@ -1236,43 +1105,32 @@ impl<'a> WindowContext<'a> {
}
fn dispatch_mouse_event(&mut self, event: &dyn Any) {
if let Some(mut handlers) = self
.window
.rendered_frame
.mouse_listeners
.remove(&event.type_id())
{
// Because handlers may add other handlers, we sort every time.
handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));
self.window.mouse_hit_test = self.window.rendered_frame.hit_test(self.mouse_position());
let mut mouse_listeners = mem::take(&mut self.window.rendered_frame.mouse_listeners);
self.with_element_context(|cx| {
// Capture phase, events bubble from back to front. Handlers for this phase are used for
// special purposes, such as detecting events outside of a given Bounds.
for (_, _, handler) in &mut handlers {
self.with_element_context(|cx| {
handler(event, DispatchPhase::Capture, cx);
});
if !self.app.propagate_event {
for listener in &mut mouse_listeners {
let listener = listener.as_mut().unwrap();
listener(event, DispatchPhase::Capture, cx);
if !cx.app.propagate_event {
break;
}
}
// Bubble phase, where most normal handlers do their work.
if self.app.propagate_event {
for (_, _, handler) in handlers.iter_mut().rev() {
self.with_element_context(|cx| {
handler(event, DispatchPhase::Bubble, cx);
});
if !self.app.propagate_event {
if cx.app.propagate_event {
for listener in mouse_listeners.iter_mut().rev() {
let listener = listener.as_mut().unwrap();
listener(event, DispatchPhase::Bubble, cx);
if !cx.app.propagate_event {
break;
}
}
}
self.window
.rendered_frame
.mouse_listeners
.insert(event.type_id(), handlers);
}
});
self.window.rendered_frame.mouse_listeners = mouse_listeners;
if self.app.propagate_event && self.has_active_drag() {
if event.is::<MouseMoveEvent>() {
@@ -1342,6 +1200,7 @@ impl<'a> WindowContext<'a> {
})
.log_err();
}));
self.window.pending_input = Some(currently_pending);
self.propagate_event = false;
@@ -1522,14 +1381,6 @@ impl<'a> WindowContext<'a> {
}
}
/// Toggle the graphics profiler to debug your application's rendering performance.
pub fn toggle_graphics_profiler(&mut self) {
self.window.graphics_profiler_enabled = !self.window.graphics_profiler_enabled;
self.window
.platform_window
.set_graphics_profiler_enabled(self.window.graphics_profiler_enabled);
}
/// Register the given handler to be invoked whenever the global of the given type
/// is updated.
pub fn observe_global<G: Global>(
@@ -1658,12 +1509,11 @@ impl<'a> WindowContext<'a> {
}
pub(crate) fn parent_view_id(&self) -> EntityId {
*self
.window
self.window
.next_frame
.view_stack
.last()
.expect("a view should always be on the stack while drawing")
.dispatch_tree
.parent_view_id()
.unwrap()
}
/// Register an action listener on the window for the next frame. The type of action
@@ -2694,12 +2544,6 @@ impl Display for ElementId {
}
}
impl ElementId {
pub(crate) fn from_entity_id(entity_id: EntityId) -> Self {
ElementId::View(entity_id)
}
}
impl TryInto<SharedString> for ElementId {
type Error = anyhow::Error;

File diff suppressed because it is too large Load Diff

View File

@@ -13,10 +13,6 @@ pub fn derive_into_element(input: TokenStream) -> TokenStream {
{
type Element = gpui::Component<Self>;
fn element_id(&self) -> Option<gpui::ElementId> {
None
}
fn into_element(self) -> Self::Element {
gpui::Component::new(self)
}

View File

@@ -12,7 +12,7 @@ pub fn derive_render(input: TokenStream) -> TokenStream {
#where_clause
{
fn render(&mut self, _cx: &mut gpui::ViewContext<Self>) -> impl gpui::Element {
()
gpui::Empty
}
}
};

Some files were not shown because too many files have changed in this diff Show More