commit
4faee3956e
15 changed files with 855 additions and 826 deletions
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -1 +1,8 @@
|
|||
/target
|
||||
/.cargo/
|
||||
/debian/*debhelper*
|
||||
/debian/cosmic-greeter.substvars
|
||||
/debian/cosmic-greeter/
|
||||
/debian/files
|
||||
/target/
|
||||
/vendor.tar
|
||||
/vendor/
|
||||
|
|
|
|||
150
Cargo.lock
generated
150
Cargo.lock
generated
|
|
@ -99,9 +99,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
|||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
version = "0.7.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
|
|
@ -110,9 +110,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.4"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72832d73be48bac96a5d7944568f305d829ed55b0ce3b483647089dfaf6cf704"
|
||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
|
|
@ -303,7 +303,7 @@ dependencies = [
|
|||
"log",
|
||||
"parking",
|
||||
"polling 2.8.0",
|
||||
"rustix 0.37.26",
|
||||
"rustix 0.37.27",
|
||||
"slab",
|
||||
"socket2",
|
||||
"waker-fn",
|
||||
|
|
@ -331,7 +331,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"event-listener 3.0.0",
|
||||
"futures-lite",
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
|
|
@ -358,7 +358,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.48.0",
|
||||
|
|
@ -405,7 +405,7 @@ version = "0.4.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4d45f362125ed144544e57b0ec6de8fd6a296d41a6252fc4a20c0cf12e9ed3a"
|
||||
dependencies = [
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"tempfile",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
|
@ -624,7 +624,7 @@ dependencies = [
|
|||
"bitflags 2.4.1",
|
||||
"log",
|
||||
"polling 3.2.0",
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"slab",
|
||||
"thiserror",
|
||||
]
|
||||
|
|
@ -636,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
|
||||
dependencies = [
|
||||
"calloop 0.12.3",
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"wayland-backend 0.3.2",
|
||||
"wayland-client 0.31.1",
|
||||
]
|
||||
|
|
@ -839,7 +839,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"atomicwrites",
|
||||
"cosmic-config-derive",
|
||||
|
|
@ -853,7 +853,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-config-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
|
|
@ -895,7 +895,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-text"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-text?branch=vi-editor#c1e40363ab576c90edb7b78f1f257b3845558b1a"
|
||||
source = "git+https://github.com/pop-os/cosmic-text?branch=vi-editor#423fc2243930645036ff053e384a3ce64e70255e"
|
||||
dependencies = [
|
||||
"fontdb 0.15.0",
|
||||
"libm",
|
||||
|
|
@ -916,7 +916,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cosmic-theme"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"almost",
|
||||
"cosmic-config",
|
||||
|
|
@ -929,9 +929,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4"
|
||||
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -1638,9 +1638,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -1653,9 +1653,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
|
@ -1663,15 +1663,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
|
@ -1681,9 +1681,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
|
|
@ -1702,9 +1702,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1713,21 +1713,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
version = "0.3.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -1986,7 +1986,7 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash 0.7.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1995,7 +1995,7 @@ version = "0.14.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
dependencies = [
|
||||
"ahash 0.8.4",
|
||||
"ahash 0.8.6",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
|
|
@ -2047,7 +2047,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
|||
[[package]]
|
||||
name = "iced"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"iced_accessibility",
|
||||
"iced_core",
|
||||
|
|
@ -2063,7 +2063,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_accessibility"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_unix",
|
||||
|
|
@ -2073,7 +2073,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_core"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"iced_accessibility",
|
||||
|
|
@ -2088,7 +2088,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_futures"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"iced_core",
|
||||
|
|
@ -2100,7 +2100,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_graphics"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bytemuck",
|
||||
|
|
@ -2118,7 +2118,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_renderer"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_tiny_skia",
|
||||
|
|
@ -2131,7 +2131,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_runtime"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"iced_accessibility",
|
||||
"iced_core",
|
||||
|
|
@ -2143,7 +2143,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_sctk"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"enum-repr",
|
||||
"float-cmp",
|
||||
|
|
@ -2167,7 +2167,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_style"
|
||||
version = "0.9.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"iced_core",
|
||||
"once_cell",
|
||||
|
|
@ -2177,7 +2177,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_tiny_skia"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"cosmic-text 0.9.0",
|
||||
|
|
@ -2195,7 +2195,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_wgpu"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"bytemuck",
|
||||
|
|
@ -2217,7 +2217,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_widget"
|
||||
version = "0.1.3"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"iced_renderer",
|
||||
"iced_runtime",
|
||||
|
|
@ -2232,7 +2232,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "iced_winit"
|
||||
version = "0.10.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"iced_graphics",
|
||||
"iced_runtime",
|
||||
|
|
@ -2357,7 +2357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
|
|
@ -2479,7 +2479,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
|||
[[package]]
|
||||
name = "libcosmic"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/libcosmic#a2bda491520093c3343018204bfc8c4169b5e3b9"
|
||||
source = "git+https://github.com/pop-os/libcosmic?branch=menu#4e5e1a7f54ed117c066289758c7b3dfe0487be2c"
|
||||
dependencies = [
|
||||
"apply",
|
||||
"ashpd",
|
||||
|
|
@ -3483,7 +3483,7 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.20",
|
||||
"rustix 0.38.21",
|
||||
"tracing",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
|
@ -3841,9 +3841,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.26"
|
||||
version = "0.37.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995"
|
||||
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
|
|
@ -3855,9 +3855,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.20"
|
||||
version = "0.38.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
|
||||
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
|
|
@ -3991,18 +3991,18 @@ checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.189"
|
||||
version = "1.0.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.189"
|
||||
version = "1.0.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -4375,14 +4375,14 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.0"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
||||
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand 2.0.1",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix 0.38.20",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.21",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
|
|
@ -4548,21 +4548,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.3"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b150d2f463da7b52f12110d3995dc86598bf90d535e929e5f5af15ab89155011"
|
||||
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.3",
|
||||
"toml_edit 0.20.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.4"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51cc078118ed25af325985ff674c00c8416b0f962be67da4946854ebfc99f334"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
|
@ -4580,9 +4580,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.20.3"
|
||||
version = "0.20.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a2534c1aa199edef7108fb7d970facaa17f8f8cc5ce6bde75372cfa1051ed91"
|
||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||
dependencies = [
|
||||
"indexmap 2.0.2",
|
||||
"serde",
|
||||
|
|
@ -5854,18 +5854,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
|
|||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.11"
|
||||
version = "0.7.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c19fae0c8a9efc6a8281f2e623db8af1db9e57852e04cde3e754dd2dc29340f"
|
||||
checksum = "a1808c6e5aa42cb2b67578276190258f08679c275d92a19735aad5d359e1b154"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.11"
|
||||
version = "0.7.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc56589e9ddd1f1c28d4b4b5c773ce232910a6bb67a70133d61c9e347585efe9"
|
||||
checksum = "ea597733532c780ce3e110401fc3b359b984cc78e17ac0917d3f371ffd1c1618"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ features = ["syntect", "vi"]
|
|||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
branch = "menu"
|
||||
default-features = false
|
||||
#path = "../libcosmic"
|
||||
|
||||
[features]
|
||||
default = ["wayland"]
|
||||
|
|
|
|||
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
cosmic-edit (0.1.0) jammy; urgency=medium
|
||||
|
||||
* Initial release.
|
||||
|
||||
-- Jeremy Soller <jeremy@system76.com> Tue, 03 Oct 2023 07:37:28 -0600
|
||||
19
debian/control
vendored
Normal file
19
debian/control
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
Source: cosmic-edit
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Maintainer: Jeremy Soller <jeremy@system76.com>
|
||||
Build-Depends:
|
||||
debhelper-compat (=13),
|
||||
just (>= 1.13.0),
|
||||
libclang-dev,
|
||||
libwayland-dev,
|
||||
libxkbcommon-dev,
|
||||
pkg-config,
|
||||
rust-all,
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/pop-os/cosmic-edit
|
||||
|
||||
Package: cosmic-edit
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: Cosmic Text Editor
|
||||
7
debian/copyright
vendored
Normal file
7
debian/copyright
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
Upstream-Name: cosmic-edit
|
||||
Upstream-Contact: Jeremy Soller <jeremy@system76.com>
|
||||
Source: https://github.com/pop-os/cosmic-edit
|
||||
Files: *
|
||||
Copyright: System76 <info@system76.com>
|
||||
License: GPL-3.0
|
||||
22
debian/rules
vendored
Executable file
22
debian/rules
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export DESTDIR = debian/cosmic-edit
|
||||
export VENDOR ?= 1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_clean:
|
||||
if ! ischroot && test "${VENDOR}" = "1"; then \
|
||||
mkdir -p .cargo; \
|
||||
cargo vendor | head -n -1 > .cargo/config; \
|
||||
echo 'directory = "vendor"' >> .cargo/config; \
|
||||
tar pcf vendor.tar vendor; \
|
||||
rm -rf vendor; \
|
||||
fi
|
||||
|
||||
override_dh_auto_build:
|
||||
just build-vendored
|
||||
|
||||
override_dh_auto_install:
|
||||
just rootdir=$(DESTDIR) install
|
||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.0 (native)
|
||||
4
debian/source/options
vendored
Normal file
4
debian/source/options
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
tar-ignore=.github
|
||||
tar-ignore=.vscode
|
||||
tar-ignore=vendor
|
||||
tar-ignore=target
|
||||
66
justfile
Normal file
66
justfile
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
name := 'cosmic-edit'
|
||||
export APPID := 'com.system76.CosmicEdit'
|
||||
|
||||
rootdir := ''
|
||||
prefix := '/usr'
|
||||
|
||||
base-dir := absolute_path(clean(rootdir / prefix))
|
||||
|
||||
export INSTALL_DIR := base-dir / 'share'
|
||||
|
||||
bin-src := 'target' / 'release' / name
|
||||
bin-dst := base-dir / 'bin' / name
|
||||
|
||||
# Default recipe which runs `just build-release`
|
||||
default: build-release
|
||||
|
||||
# Runs `cargo clean`
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
# `cargo clean` and removes vendored dependencies
|
||||
clean-dist: clean
|
||||
rm -rf .cargo vendor vendor.tar
|
||||
|
||||
# Compiles with debug profile
|
||||
build-debug *args:
|
||||
cargo build {{args}}
|
||||
|
||||
# Compiles with release profile
|
||||
build-release *args: (build-debug '--release' args)
|
||||
|
||||
# Compiles release profile with vendored dependencies
|
||||
build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
|
||||
|
||||
# Runs a clippy check
|
||||
check *args:
|
||||
cargo clippy --all-features {{args}} -- -W clippy::pedantic
|
||||
|
||||
# Runs a clippy check with JSON message format
|
||||
check-json: (check '--message-format=json')
|
||||
|
||||
# Run with debug logs
|
||||
run *args:
|
||||
env RUST_LOG=debug RUST_BACKTRACE=full cargo run --release {{args}}
|
||||
|
||||
# Installs files
|
||||
install:
|
||||
install -Dm0755 {{bin-src}} {{bin-dst}}
|
||||
|
||||
# Uninstalls installed files
|
||||
uninstall:
|
||||
rm {{bin-dst}}
|
||||
|
||||
# Vendor dependencies locally
|
||||
vendor:
|
||||
mkdir -p .cargo
|
||||
cargo vendor --sync Cargo.toml \
|
||||
| head -n -1 > .cargo/config
|
||||
echo 'directory = "vendor"' >> .cargo/config
|
||||
tar pcf vendor.tar vendor
|
||||
rm -rf vendor
|
||||
|
||||
# Extracts vendored dependencies
|
||||
vendor-extract:
|
||||
rm -rf vendor
|
||||
tar pxf vendor.tar
|
||||
450
src/main.rs
450
src/main.rs
|
|
@ -1,40 +1,41 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
app::{Command, Core, Settings},
|
||||
app::{message, Command, Core, Settings},
|
||||
executor,
|
||||
iced::{
|
||||
widget::{column, row, text},
|
||||
widget::{row, text},
|
||||
Alignment, Length, Limits,
|
||||
},
|
||||
widget::{self, icon, segmented_button, view_switcher},
|
||||
style,
|
||||
widget::{self, button, icon, nav_bar, segmented_button, view_switcher},
|
||||
ApplicationExt, Element,
|
||||
};
|
||||
use cosmic_text::{
|
||||
Attrs, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, ViEditor, ViMode,
|
||||
use cosmic_text::{FontSystem, SyntaxSystem, ViMode};
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::Mutex,
|
||||
};
|
||||
use std::{env, fs, path::PathBuf, sync::Mutex};
|
||||
|
||||
use self::menu_list::MenuList;
|
||||
mod menu_list;
|
||||
use self::menu::menu_bar;
|
||||
mod menu;
|
||||
|
||||
use self::project::ProjectNode;
|
||||
mod project;
|
||||
|
||||
use self::tab::Tab;
|
||||
mod tab;
|
||||
|
||||
use self::text_box::text_box;
|
||||
mod text_box;
|
||||
|
||||
//TODO: re-use iced FONT_SYSTEM
|
||||
lazy_static::lazy_static! {
|
||||
static ref FONT_SYSTEM: Mutex<FontSystem> = Mutex::new(FontSystem::new());
|
||||
static ref SYNTAX_SYSTEM: SyntaxSystem = SyntaxSystem::new();
|
||||
}
|
||||
|
||||
static FONT_SIZES: &'static [Metrics] = &[
|
||||
Metrics::new(10.0, 14.0), // Caption
|
||||
Metrics::new(14.0, 20.0), // Body
|
||||
Metrics::new(20.0, 28.0), // Title 4
|
||||
Metrics::new(24.0, 32.0), // Title 3
|
||||
Metrics::new(28.0, 36.0), // Title 2
|
||||
Metrics::new(32.0, 44.0), // Title 1
|
||||
];
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
||||
|
||||
|
|
@ -45,99 +46,38 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub struct Tab {
|
||||
path_opt: Option<PathBuf>,
|
||||
attrs: Attrs<'static>,
|
||||
editor: Mutex<ViEditor<'static>>,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
wrap: bool,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
impl Config {
|
||||
//TODO: load from cosmic-config
|
||||
pub fn new() -> Self {
|
||||
let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace);
|
||||
|
||||
let editor = SyntaxEditor::new(
|
||||
Buffer::new(&mut FONT_SYSTEM.lock().unwrap(), FONT_SIZES[1 /* Body */]),
|
||||
&SYNTAX_SYSTEM,
|
||||
"base16-eighties.dark",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut editor = ViEditor::new(editor);
|
||||
editor.set_passthrough(false);
|
||||
|
||||
Self {
|
||||
path_opt: None,
|
||||
attrs,
|
||||
editor: Mutex::new(editor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open(&mut self, path: PathBuf) {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
||||
let mut editor = editor.borrow_with(&mut font_system);
|
||||
match editor.load_text(&path, self.attrs) {
|
||||
Ok(()) => {
|
||||
log::info!("opened '{}'", path.display());
|
||||
self.path_opt = Some(path);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to open '{}': {}", path.display(), err);
|
||||
self.path_opt = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
if let Some(path) = &self.path_opt {
|
||||
let editor = self.editor.lock().unwrap();
|
||||
let mut text = String::new();
|
||||
for line in editor.buffer().lines.iter() {
|
||||
text.push_str(line.text());
|
||||
text.push('\n');
|
||||
}
|
||||
match fs::write(path, text) {
|
||||
Ok(()) => {
|
||||
log::info!("saved '{}'", path.display());
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to save '{}': {}", path.display(), err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("tab has no path yet");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
//TODO: show full title when there is a conflict
|
||||
if let Some(path) = &self.path_opt {
|
||||
match path.file_name() {
|
||||
Some(file_name_os) => match file_name_os.to_str() {
|
||||
Some(file_name) => file_name.to_string(),
|
||||
None => format!("{}", path.display()),
|
||||
},
|
||||
None => format!("{}", path.display()),
|
||||
}
|
||||
} else {
|
||||
"New document".to_string()
|
||||
}
|
||||
Self { wrap: false }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
core: Core,
|
||||
nav_model: segmented_button::SingleSelectModel,
|
||||
tab_model: segmented_button::SingleSelectModel,
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
Open,
|
||||
New,
|
||||
OpenFileDialog,
|
||||
OpenFile(PathBuf),
|
||||
OpenProjectDialog,
|
||||
OpenProject(PathBuf),
|
||||
Save,
|
||||
TabActivate(segmented_button::Entity),
|
||||
TabClose(segmented_button::Entity),
|
||||
Todo,
|
||||
Wrap(bool),
|
||||
}
|
||||
|
||||
impl App {
|
||||
|
|
@ -149,25 +89,168 @@ impl App {
|
|||
self.tab_model.active_data_mut()
|
||||
}
|
||||
|
||||
fn open_folder<P: AsRef<Path>>(&mut self, path: P, mut position: u16, indent: u16) {
|
||||
let read_dir = match fs::read_dir(&path) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::error!("failed to read directory {:?}: {}", path.as_ref(), err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for entry_res in read_dir {
|
||||
let entry = match entry_res {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"failed to read entry in directory {:?}: {}",
|
||||
path.as_ref(),
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let entry_path = entry.path();
|
||||
let node = match ProjectNode::new(&entry_path) {
|
||||
Ok(ok) => ok,
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"failed to open directory {:?} entry {:?}: {}",
|
||||
path.as_ref(),
|
||||
entry_path,
|
||||
err
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
nodes.sort();
|
||||
|
||||
for node in nodes {
|
||||
self.nav_model
|
||||
.insert()
|
||||
.position(position)
|
||||
.indent(indent)
|
||||
.icon(icon::from_name(node.icon_name()).size(16).icon())
|
||||
.text(node.name().to_string())
|
||||
.data(node);
|
||||
|
||||
position += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_project<P: AsRef<Path>>(&mut self, path: P) {
|
||||
let node = match ProjectNode::new(&path) {
|
||||
Ok(mut node) => {
|
||||
match &mut node {
|
||||
ProjectNode::Folder { open, root, .. } => {
|
||||
*open = true;
|
||||
*root = true;
|
||||
}
|
||||
_ => {
|
||||
log::error!(
|
||||
"failed to open project {:?}: not a directory",
|
||||
path.as_ref()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
node
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to open project {:?}: {}", path.as_ref(), err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let id = self
|
||||
.nav_model
|
||||
.insert()
|
||||
.icon(icon::from_name(node.icon_name()).size(16).icon())
|
||||
.text(node.name().to_string())
|
||||
.data(node)
|
||||
.id();
|
||||
|
||||
let position = self.nav_model.position(id).unwrap_or(0);
|
||||
|
||||
self.open_folder(&path, position + 1, 1);
|
||||
}
|
||||
|
||||
pub fn open_tab(&mut self, path_opt: Option<PathBuf>) {
|
||||
let mut tab = Tab::new();
|
||||
tab.set_config(&self.config);
|
||||
if let Some(path) = path_opt {
|
||||
tab.open(path);
|
||||
}
|
||||
self.tab_model
|
||||
.insert()
|
||||
.text(tab.title())
|
||||
.icon(icon::from_name("text-x-generic").icon())
|
||||
.data(tab)
|
||||
.icon(icon::from_name("text-x-generic").size(16).icon())
|
||||
.data::<Tab>(tab)
|
||||
.closable()
|
||||
.activate();
|
||||
}
|
||||
|
||||
fn update_nav_bar_active(&mut self) {
|
||||
let tab_path_opt = match self.active_tab() {
|
||||
Some(tab) => tab.path_opt.clone(),
|
||||
None => None,
|
||||
};
|
||||
|
||||
// Locate tree node to activate
|
||||
let mut active_id = segmented_button::Entity::default();
|
||||
match tab_path_opt {
|
||||
Some(tab_path) => {
|
||||
// Automatically expand tree to find and select active file
|
||||
loop {
|
||||
let mut expand_opt = None;
|
||||
for id in self.nav_model.iter() {
|
||||
match self.nav_model.data(id) {
|
||||
Some(node) => match node {
|
||||
ProjectNode::Folder { path, open, .. } => {
|
||||
if tab_path.starts_with(path) && !*open {
|
||||
expand_opt = Some(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ProjectNode::File { path, .. } => {
|
||||
if path == &tab_path {
|
||||
active_id = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
match expand_opt {
|
||||
Some(id) => {
|
||||
//TODO: can this be optimized?
|
||||
cosmic::Application::on_nav_select(self, id);
|
||||
}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.nav_model.activate(active_id);
|
||||
}
|
||||
|
||||
pub fn update_title(&mut self) -> Command<Message> {
|
||||
self.update_nav_bar_active();
|
||||
|
||||
let title = match self.active_tab() {
|
||||
Some(tab) => tab.title(),
|
||||
None => format!("No Open File"),
|
||||
};
|
||||
|
||||
let window_title = format!("{title} - COSMIC Text Editor");
|
||||
self.set_header_title(title.clone());
|
||||
self.set_window_title(window_title)
|
||||
|
|
@ -186,7 +269,7 @@ impl cosmic::Application for App {
|
|||
type Message = Message;
|
||||
|
||||
/// The unique application ID to supply to the window manager.
|
||||
const APP_ID: &'static str = "com.system76.CosmicTextEditor";
|
||||
const APP_ID: &'static str = "com.system76.CosmicEdit";
|
||||
|
||||
fn core(&self) -> &Core {
|
||||
&self.core
|
||||
|
|
@ -200,11 +283,27 @@ impl cosmic::Application for App {
|
|||
fn init(core: Core, _flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||
let mut app = App {
|
||||
core,
|
||||
nav_model: nav_bar::Model::builder().build(),
|
||||
tab_model: segmented_button::Model::builder().build(),
|
||||
config: Config::new(),
|
||||
};
|
||||
|
||||
for path in env::args().skip(1) {
|
||||
app.open_tab(Some(PathBuf::from(path)));
|
||||
for arg in env::args().skip(1) {
|
||||
let path = PathBuf::from(arg);
|
||||
if path.is_dir() {
|
||||
app.open_project(path);
|
||||
} else {
|
||||
app.open_tab(Some(path));
|
||||
}
|
||||
}
|
||||
|
||||
// Show nav bar only if project is provided
|
||||
if app.core.nav_bar_active() != app.nav_model.iter().next().is_some() {
|
||||
app.core.nav_bar_toggle();
|
||||
app.nav_model
|
||||
.insert()
|
||||
.icon(icon::from_name("folder-open-symbolic").size(16).icon())
|
||||
.text("Open project");
|
||||
}
|
||||
|
||||
// Open an empty file if no arguments provided
|
||||
|
|
@ -216,13 +315,104 @@ impl cosmic::Application for App {
|
|||
(app, command)
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Self::Message> {
|
||||
match message {
|
||||
Message::Open => {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
||||
self.open_tab(Some(path));
|
||||
return self.update_title();
|
||||
fn nav_model(&self) -> Option<&nav_bar::Model> {
|
||||
Some(&self.nav_model)
|
||||
}
|
||||
|
||||
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Message> {
|
||||
// Toggle open state and get clone of node data
|
||||
let node_opt = match self.nav_model.data_mut::<ProjectNode>(id) {
|
||||
Some(node) => {
|
||||
match node {
|
||||
ProjectNode::Folder { open, .. } => {
|
||||
*open = !*open;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Some(node.clone())
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
match node_opt {
|
||||
Some(node) => {
|
||||
// Update icon
|
||||
self.nav_model
|
||||
.icon_set(id, icon::from_name(node.icon_name()).size(16).icon());
|
||||
|
||||
match node {
|
||||
ProjectNode::Folder { path, open, .. } => {
|
||||
let position = self.nav_model.position(id).unwrap_or(0);
|
||||
let indent = self.nav_model.indent(id).unwrap_or(0);
|
||||
if open {
|
||||
// Open folder
|
||||
self.open_folder(path, position + 1, indent + 1);
|
||||
} else {
|
||||
// Close folder
|
||||
loop {
|
||||
let child_id = match self.nav_model.entity_at(position + 1) {
|
||||
Some(some) => some,
|
||||
None => break,
|
||||
};
|
||||
|
||||
if self.nav_model.indent(child_id).unwrap_or(0) > indent {
|
||||
self.nav_model.remove(child_id);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::none()
|
||||
}
|
||||
ProjectNode::File { path, .. } => {
|
||||
//TODO: go to already open file if possible
|
||||
self.update(Message::OpenFile(path))
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Open project
|
||||
self.update(Message::OpenProjectDialog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> Command<Message> {
|
||||
match message {
|
||||
Message::New => {
|
||||
self.open_tab(None);
|
||||
return self.update_title();
|
||||
}
|
||||
Message::OpenFileDialog => {
|
||||
return Command::perform(
|
||||
async {
|
||||
if let Some(handle) = rfd::AsyncFileDialog::new().pick_file().await {
|
||||
message::app(Message::OpenFile(handle.path().to_owned()))
|
||||
} else {
|
||||
message::none()
|
||||
}
|
||||
},
|
||||
|x| x,
|
||||
);
|
||||
}
|
||||
Message::OpenFile(path) => {
|
||||
self.open_tab(Some(path));
|
||||
return self.update_title();
|
||||
}
|
||||
Message::OpenProjectDialog => {
|
||||
return Command::perform(
|
||||
async {
|
||||
if let Some(handle) = rfd::AsyncFileDialog::new().pick_folder().await {
|
||||
message::app(Message::OpenProject(handle.path().to_owned()))
|
||||
} else {
|
||||
message::none()
|
||||
}
|
||||
},
|
||||
|x| x,
|
||||
);
|
||||
}
|
||||
Message::OpenProject(path) => {
|
||||
self.open_project(path);
|
||||
}
|
||||
Message::Save => {
|
||||
let mut title_opt = None;
|
||||
|
|
@ -230,6 +420,7 @@ impl cosmic::Application for App {
|
|||
match self.active_tab_mut() {
|
||||
Some(tab) => {
|
||||
if tab.path_opt.is_none() {
|
||||
//TODO: use async file dialog
|
||||
tab.path_opt = rfd::FileDialog::new().save_file();
|
||||
title_opt = Some(tab.title());
|
||||
}
|
||||
|
|
@ -271,37 +462,40 @@ impl cosmic::Application for App {
|
|||
Message::Todo => {
|
||||
log::warn!("TODO");
|
||||
}
|
||||
Message::Wrap(wrap) => {
|
||||
self.config.wrap = wrap;
|
||||
//TODO: provide iterator over data
|
||||
let entities: Vec<_> = self.tab_model.iter().collect();
|
||||
for entity in entities {
|
||||
if let Some(tab) = self.tab_model.data_mut::<Tab>(entity) {
|
||||
tab.set_config(&self.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let menu_bar = row![
|
||||
MenuList::new(vec!["Open", "Save"], None, |item| {
|
||||
match item {
|
||||
"Open" => Message::Open,
|
||||
"Save" => Message::Save,
|
||||
_ => Message::Todo,
|
||||
}
|
||||
})
|
||||
.padding(8)
|
||||
.placeholder("File"),
|
||||
MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("Edit"),
|
||||
MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("View"),
|
||||
MenuList::new(vec!["Todo"], None, |_| Message::Todo).placeholder("Help"),
|
||||
]
|
||||
.align_items(Alignment::Start)
|
||||
.padding(4)
|
||||
.spacing(16);
|
||||
fn header_start(&self) -> Vec<Element<Message>> {
|
||||
vec![menu_bar(&self.config)]
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<Message> {
|
||||
let mut tab_column = widget::column::with_capacity(3).padding([0, 16]);
|
||||
|
||||
tab_column = tab_column.push(
|
||||
view_switcher::horizontal(&self.tab_model)
|
||||
.on_activate(Message::TabActivate)
|
||||
.on_close(Message::TabClose)
|
||||
.width(Length::Shrink),
|
||||
row![
|
||||
view_switcher::horizontal(&self.tab_model)
|
||||
.on_activate(Message::TabActivate)
|
||||
.on_close(Message::TabClose)
|
||||
.width(Length::Shrink),
|
||||
button(icon::from_name("list-add-symbolic").size(16).icon())
|
||||
.on_press(Message::New)
|
||||
.padding(8)
|
||||
.style(style::Button::Icon)
|
||||
]
|
||||
.align_items(Alignment::Center),
|
||||
);
|
||||
|
||||
match self.active_tab() {
|
||||
|
|
@ -337,10 +531,10 @@ impl cosmic::Application for App {
|
|||
}
|
||||
};
|
||||
|
||||
let content: Element<_> = column![menu_bar, tab_column].into();
|
||||
let content: Element<_> = tab_column.into();
|
||||
|
||||
// Uncomment to debug layout:
|
||||
//content.explain(Color::WHITE)
|
||||
//content.explain(cosmic::iced::Color::WHITE)
|
||||
content
|
||||
}
|
||||
}
|
||||
|
|
|
|||
124
src/menu.rs
Normal file
124
src/menu.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
//TODO: export in cosmic::widget
|
||||
iced::{widget::horizontal_rule, Alignment, Length},
|
||||
theme,
|
||||
widget::{
|
||||
self, horizontal_space,
|
||||
menu::{ItemHeight, ItemWidth, MenuBar, MenuTree},
|
||||
},
|
||||
Element,
|
||||
};
|
||||
|
||||
use crate::{Config, Message};
|
||||
|
||||
pub fn menu_bar<'a>(config: &Config) -> Element<'a, Message> {
|
||||
//TODO: port to libcosmic
|
||||
let menu_root = |label| {
|
||||
widget::button(label)
|
||||
.padding([4, 12])
|
||||
.style(theme::Button::MenuRoot)
|
||||
};
|
||||
|
||||
macro_rules! menu_button {
|
||||
($($x:expr),+ $(,)?) => (
|
||||
widget::button(
|
||||
widget::Row::with_children(
|
||||
vec![$(Element::from($x)),+]
|
||||
)
|
||||
.align_items(Alignment::Center)
|
||||
)
|
||||
.height(Length::Fixed(32.0))
|
||||
.padding([4, 16])
|
||||
.width(Length::Fill)
|
||||
.style(theme::Button::MenuItem)
|
||||
);
|
||||
}
|
||||
|
||||
let menu_folder = |label| menu_button!(label, horizontal_space(Length::Fill), ">");
|
||||
|
||||
let menu_item = |label, message| MenuTree::new(menu_button!(label).on_press(message));
|
||||
|
||||
let menu_key = |label, key, message| {
|
||||
MenuTree::new(menu_button!(label, horizontal_space(Length::Fill), key).on_press(message))
|
||||
};
|
||||
|
||||
MenuBar::new(vec![
|
||||
MenuTree::with_children(
|
||||
menu_root("File"),
|
||||
vec![
|
||||
menu_key("New file", "Ctrl + N", Message::New),
|
||||
menu_key("New window", "Ctrl + Shift + N", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key("Open file...", "Ctrl + O", Message::OpenFileDialog),
|
||||
MenuTree::with_children(
|
||||
menu_folder("Open recent"),
|
||||
vec![menu_item("TODO", Message::Todo)],
|
||||
),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key("Save", "Ctrl + S", Message::Save),
|
||||
menu_key("Save as...", "Ctrl + Shift + S", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Revert all changes", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Document statistics...", Message::Todo),
|
||||
menu_item("Document type...", Message::Todo),
|
||||
menu_item("Encoding...", Message::Todo),
|
||||
menu_item("Print", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key("Quit", "Ctrl + Q", Message::Todo),
|
||||
],
|
||||
),
|
||||
MenuTree::with_children(
|
||||
menu_root("Edit"),
|
||||
vec![
|
||||
menu_key("Undo", "Ctrl + Z", Message::Todo),
|
||||
menu_key("Redo", "Ctrl + Shift + Z", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key("Cut", "Ctrl + X", Message::Todo),
|
||||
menu_key("Copy", "Ctrl + C", Message::Todo),
|
||||
menu_key("Paste", "Ctrl + V", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key("Find", "Ctrl + F", Message::Todo),
|
||||
menu_key("Replace", "Ctrl + H", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Spell check...", Message::Todo),
|
||||
],
|
||||
),
|
||||
MenuTree::with_children(
|
||||
menu_root("View"),
|
||||
vec![
|
||||
MenuTree::with_children(
|
||||
menu_folder("Indentation"),
|
||||
vec![
|
||||
menu_item("Automatic indentation", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Tab width: 1", Message::Todo),
|
||||
menu_item("Tab width: 2", Message::Todo),
|
||||
menu_item("Tab width: 4", Message::Todo),
|
||||
menu_item("Tab width: 8", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Convert indentation to spaces", Message::Todo),
|
||||
menu_item("Convert indentation to tabs", Message::Todo),
|
||||
],
|
||||
),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Word wrap", Message::Todo),
|
||||
menu_item("Show line numbers", Message::Todo),
|
||||
menu_item("Highlight current line", Message::Todo),
|
||||
menu_item("Syntax highlighting...", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_key("Settings...", "Ctrl + ,", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("Keyboard shortcuts...", Message::Todo),
|
||||
MenuTree::new(horizontal_rule(1)),
|
||||
menu_item("About COSMIC Text Editor", Message::Todo),
|
||||
],
|
||||
),
|
||||
])
|
||||
.item_height(ItemHeight::Dynamic(40))
|
||||
.item_width(ItemWidth::Uniform(240))
|
||||
.spacing(4.0)
|
||||
.into()
|
||||
}
|
||||
622
src/menu_list.rs
622
src/menu_list.rs
|
|
@ -1,622 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::{
|
||||
iced::{
|
||||
alignment,
|
||||
event::{self, Event},
|
||||
keyboard, mouse, overlay,
|
||||
overlay::menu::{self, Menu},
|
||||
touch,
|
||||
widget::container,
|
||||
widget::scrollable,
|
||||
Background, Color, Element, Length, Padding, Rectangle, Size,
|
||||
},
|
||||
iced_core::{
|
||||
clipboard::Clipboard,
|
||||
layout::{self, Layout},
|
||||
renderer,
|
||||
text::{self, LineHeight, Shaping, Text},
|
||||
widget::tree::{self, Tree},
|
||||
Shell, Widget,
|
||||
},
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// The appearance of a pick list.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Appearance {
|
||||
/// The text [`Color`] of the pick list.
|
||||
pub text_color: Color,
|
||||
/// The placeholder [`Color`] of the pick list.
|
||||
pub placeholder_color: Color,
|
||||
/// The [`Background`] of the pick list.
|
||||
pub background: Background,
|
||||
/// The border radius of the pick list.
|
||||
pub border_radius: f32,
|
||||
/// The border width of the pick list.
|
||||
pub border_width: f32,
|
||||
/// The border color of the pick list.
|
||||
pub border_color: Color,
|
||||
}
|
||||
|
||||
/// A set of rules that dictate the style of a container.
|
||||
pub trait StyleSheet {
|
||||
/// The supported style of the [`StyleSheet`].
|
||||
type Style: Default + Clone;
|
||||
|
||||
/// Produces the active [`Appearance`] of a pick list.
|
||||
fn active(&self, style: &<Self as StyleSheet>::Style) -> Appearance;
|
||||
|
||||
/// Produces the hovered [`Appearance`] of a pick list.
|
||||
fn hovered(&self, style: &<Self as StyleSheet>::Style) -> Appearance;
|
||||
}
|
||||
|
||||
impl StyleSheet for cosmic::theme::Theme {
|
||||
type Style = ();
|
||||
|
||||
fn active(&self, _style: &()) -> Appearance {
|
||||
let cosmic = &self.cosmic().primary.component;
|
||||
|
||||
Appearance {
|
||||
text_color: cosmic.on.into(),
|
||||
background: Color::TRANSPARENT.into(),
|
||||
placeholder_color: cosmic.on.into(),
|
||||
border_radius: 8.0,
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
}
|
||||
}
|
||||
|
||||
fn hovered(&self, style: &()) -> Appearance {
|
||||
let cosmic = &self.cosmic().primary.component;
|
||||
|
||||
Appearance {
|
||||
background: Background::Color(cosmic.hover.into()),
|
||||
..self.active(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A widget for selecting a single value from a list of options.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct MenuList<'a, T, Message, Renderer>
|
||||
where
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
on_selected: Box<dyn Fn(T) -> Message + 'a>,
|
||||
options: Cow<'a, [T]>,
|
||||
placeholder: Option<String>,
|
||||
selected: Option<T>,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: Renderer::Font,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> MenuList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Renderer: text::Renderer,
|
||||
<Renderer as text::Renderer>::Font: std::default::Default,
|
||||
Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style: From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
/// The default padding of a [`MenuList`].
|
||||
pub const DEFAULT_PADDING: Padding = Padding::new(8.0);
|
||||
|
||||
/// Creates a new [`MenuList`] with the given list of options, the current
|
||||
/// selected value, and the message to produce when an option is selected.
|
||||
pub fn new(
|
||||
options: impl Into<Cow<'a, [T]>>,
|
||||
selected: Option<T>,
|
||||
on_selected: impl Fn(T) -> Message + 'a,
|
||||
) -> Self {
|
||||
Self {
|
||||
on_selected: Box::new(on_selected),
|
||||
options: options.into(),
|
||||
placeholder: None,
|
||||
selected,
|
||||
width: Length::Shrink,
|
||||
text_size: None,
|
||||
padding: Self::DEFAULT_PADDING,
|
||||
font: Default::default(),
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the placeholder of the [`MenuList`].
|
||||
pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
|
||||
self.placeholder = Some(placeholder.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`MenuList`].
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Padding`] of the [`MenuList`].
|
||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
||||
self.padding = padding.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the text size of the [`MenuList`].
|
||||
pub fn text_size(mut self, size: u16) -> Self {
|
||||
self.text_size = Some(size);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the font of the [`MenuList`].
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`MenuList`].
|
||||
pub fn style(mut self, style: impl Into<<Renderer::Theme as StyleSheet>::Style>) -> Self {
|
||||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer> for MenuList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq + 'static,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style: From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State<T>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::<T>::new())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(&self, renderer: &Renderer, limits: &layout::Limits) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
&self.options,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle<f32>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.on_selected.as_ref(),
|
||||
self.selected.as_ref(),
|
||||
&self.options,
|
||||
|| tree.state.downcast_mut::<State<T>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
self.selected.as_ref(),
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State<T>>();
|
||||
|
||||
overlay(
|
||||
renderer,
|
||||
layout,
|
||||
state,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
&self.options,
|
||||
&self.on_selected,
|
||||
self.style.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> From<MenuList<'a, T, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq + 'static,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style: From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
fn from(menu_list: MenuList<'a, T, Message, Renderer>) -> Self {
|
||||
Self::new(menu_list)
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`MenuList`].
|
||||
#[derive(Debug)]
|
||||
pub struct State<T> {
|
||||
menu: menu::State,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<usize>,
|
||||
last_selection: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
/// Creates a new [`State`] for a [`MenuList`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
menu: menu::State::default(),
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
is_open: bool::default(),
|
||||
hovered_option: Option::default(),
|
||||
last_selection: Option::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for State<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`MenuList`].
|
||||
pub fn layout<Renderer, T>(
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
width: Length,
|
||||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: &Renderer::Font,
|
||||
placeholder: Option<&str>,
|
||||
options: &[T],
|
||||
) -> layout::Node
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
T: ToString,
|
||||
{
|
||||
use std::f32;
|
||||
|
||||
let limits = limits.width(width).height(Length::Shrink).pad(padding);
|
||||
|
||||
let text_size = text_size.unwrap_or_else(|| renderer.default_size() as u16);
|
||||
|
||||
let max_width = match width {
|
||||
Length::Shrink => {
|
||||
let measure = |label: &str| -> u32 {
|
||||
let size = renderer.measure(
|
||||
label,
|
||||
text_size as f32,
|
||||
LineHeight::default(),
|
||||
font.clone(),
|
||||
Size::new(f32::INFINITY, f32::INFINITY),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
|
||||
size.width.round() as u32
|
||||
};
|
||||
|
||||
placeholder.map(measure).unwrap_or(100)
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
let size = {
|
||||
let intrinsic = Size::new(max_width as f32, f32::from(text_size));
|
||||
|
||||
limits.resolve(intrinsic).pad(padding)
|
||||
};
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
/// Processes an [`Event`] and updates the [`State`] of a [`MenuList`]
|
||||
/// accordingly.
|
||||
pub fn update<'a, T, Message>(
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
on_selected: &dyn Fn(T) -> Message,
|
||||
selected: Option<&T>,
|
||||
options: &[T],
|
||||
state: impl FnOnce() -> &'a mut State<T>,
|
||||
) -> event::Status
|
||||
where
|
||||
T: PartialEq + Clone + 'a,
|
||||
{
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
let state = state();
|
||||
|
||||
let event_status = if state.is_open {
|
||||
// Event wasn't processed by overlay, so cursor was clicked either outside it's
|
||||
// bounds or on the drop-down, either way we close the overlay.
|
||||
state.is_open = false;
|
||||
|
||||
event::Status::Captured
|
||||
} else if cursor_position.is_over(layout.bounds()) {
|
||||
state.is_open = true;
|
||||
state.hovered_option = options.iter().position(|option| Some(option) == selected);
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
};
|
||||
|
||||
if let Some(last_selection) = state.last_selection.take() {
|
||||
shell.publish((on_selected)(last_selection));
|
||||
|
||||
state.is_open = false;
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event_status
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::WheelScrolled {
|
||||
delta: mouse::ScrollDelta::Lines { y, .. },
|
||||
}) => {
|
||||
let state = state();
|
||||
|
||||
if state.keyboard_modifiers.command()
|
||||
&& cursor_position.is_over(layout.bounds())
|
||||
&& !state.is_open
|
||||
{
|
||||
fn find_next<'a, T: PartialEq>(
|
||||
selected: &'a T,
|
||||
mut options: impl Iterator<Item = &'a T>,
|
||||
) -> Option<&'a T> {
|
||||
let _ = options.find(|&option| option == selected);
|
||||
|
||||
options.next()
|
||||
}
|
||||
|
||||
let next_option = if y < 0.0 {
|
||||
if let Some(selected) = selected {
|
||||
find_next(selected, options.iter())
|
||||
} else {
|
||||
options.first()
|
||||
}
|
||||
} else if y > 0.0 {
|
||||
if let Some(selected) = selected {
|
||||
find_next(selected, options.iter().rev())
|
||||
} else {
|
||||
options.last()
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(next_option) = next_option {
|
||||
shell.publish((on_selected)(next_option.clone()));
|
||||
}
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
}
|
||||
}
|
||||
Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {
|
||||
let state = state();
|
||||
|
||||
state.keyboard_modifiers = modifiers;
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
_ => event::Status::Ignored,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of a [`MenuList`].
|
||||
pub fn mouse_interaction(layout: Layout<'_>, cursor_position: mouse::Cursor) -> mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = cursor_position.is_over(bounds);
|
||||
|
||||
if is_mouse_over {
|
||||
mouse::Interaction::Pointer
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current overlay of a [`MenuList`].
|
||||
pub fn overlay<'a, T, Message, Renderer>(
|
||||
renderer: &Renderer,
|
||||
layout: Layout<'_>,
|
||||
state: &'a mut State<T>,
|
||||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: Renderer::Font,
|
||||
options: &'a [T],
|
||||
on_selected: &'a dyn Fn(T) -> Message,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
) -> Option<overlay::Element<'a, Message, Renderer>>
|
||||
where
|
||||
T: Clone + ToString,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet + container::StyleSheet,
|
||||
<Renderer::Theme as menu::StyleSheet>::Style: From<<Renderer::Theme as StyleSheet>::Style>,
|
||||
{
|
||||
if state.is_open {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let text_size = text_size.unwrap_or_else(|| renderer.default_size() as u16);
|
||||
|
||||
let width = {
|
||||
let measure = |label: &str| -> u32 {
|
||||
let size = renderer.measure(
|
||||
label,
|
||||
text_size as f32,
|
||||
LineHeight::default(),
|
||||
font.clone(),
|
||||
Size::new(f32::INFINITY, f32::INFINITY),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
|
||||
size.width.round() as u32
|
||||
};
|
||||
|
||||
let labels = options.iter().map(ToString::to_string);
|
||||
|
||||
let labels_width = labels.map(|label| measure(&label)).max().unwrap_or(100);
|
||||
|
||||
labels_width as f32 + padding.left + padding.right
|
||||
};
|
||||
|
||||
let menu = Menu::new(
|
||||
&mut state.menu,
|
||||
options,
|
||||
&mut state.hovered_option,
|
||||
|option| {
|
||||
state.is_open = false;
|
||||
|
||||
(on_selected)(option)
|
||||
},
|
||||
None,
|
||||
)
|
||||
.width(width)
|
||||
.padding(padding)
|
||||
.font(font)
|
||||
.style(style)
|
||||
.text_size(text_size);
|
||||
|
||||
Some(menu.overlay(layout.position(), bounds.height))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws a [`MenuList`].
|
||||
pub fn draw<T, Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
padding: Padding,
|
||||
text_size: Option<u16>,
|
||||
font: &Renderer::Font,
|
||||
placeholder: Option<&str>,
|
||||
selected: Option<&T>,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
T: ToString,
|
||||
{
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = cursor_position.is_over(bounds);
|
||||
let is_selected = selected.is_some();
|
||||
|
||||
let style = if is_mouse_over {
|
||||
theme.hovered(style)
|
||||
} else {
|
||||
theme.active(style)
|
||||
};
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds,
|
||||
border_color: style.border_color,
|
||||
border_width: style.border_width,
|
||||
border_radius: style.border_radius.into(),
|
||||
},
|
||||
style.background,
|
||||
);
|
||||
|
||||
let label = selected.map(ToString::to_string);
|
||||
|
||||
if let Some(label) = label.as_deref().or(placeholder) {
|
||||
let text_size = text_size.map_or_else(|| renderer.default_size(), f32::from);
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: label,
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + f32::from(padding.left),
|
||||
y: bounds.center_y() - text_size / 2.0,
|
||||
width: bounds.width - f32::from(padding.horizontal()),
|
||||
height: text_size,
|
||||
},
|
||||
size: text_size,
|
||||
line_height: LineHeight::default(),
|
||||
color: if is_selected {
|
||||
style.text_color
|
||||
} else {
|
||||
style.placeholder_color
|
||||
},
|
||||
font: font.clone(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
shaping: Shaping::Advanced,
|
||||
});
|
||||
}
|
||||
}
|
||||
90
src/project.rs
Normal file
90
src/project.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fs, io,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ProjectNode {
|
||||
Folder {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
open: bool,
|
||||
root: bool,
|
||||
},
|
||||
File {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
impl ProjectNode {
|
||||
pub fn new<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
let path = fs::canonicalize(path)?;
|
||||
let name = path
|
||||
.file_name()
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("path {:?} has no file name", path),
|
||||
))?
|
||||
.to_str()
|
||||
.ok_or(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("path {:?} is not valid UTF-8", path),
|
||||
))?
|
||||
.to_string();
|
||||
Ok(if path.is_dir() {
|
||||
Self::Folder {
|
||||
path,
|
||||
name,
|
||||
open: false,
|
||||
root: false,
|
||||
}
|
||||
} else {
|
||||
Self::File { path, name }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn icon_name(&self) -> &str {
|
||||
match self {
|
||||
//TODO: different icon for project root?
|
||||
Self::Folder { open, .. } => {
|
||||
if *open {
|
||||
"go-down-symbolic"
|
||||
} else {
|
||||
"go-next-symbolic"
|
||||
}
|
||||
}
|
||||
Self::File { .. } => "text-x-generic",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::Folder { name, .. } => name,
|
||||
Self::File { name, .. } => name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for ProjectNode {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
match self {
|
||||
// Folders are always before files
|
||||
Self::Folder { .. } => match other {
|
||||
Self::File { .. } => return Ordering::Less,
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
self.name().cmp(other.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for ProjectNode {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
110
src/tab.rs
Normal file
110
src/tab.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic_text::{Attrs, Buffer, Edit, Metrics, SyntaxEditor, ViEditor, Wrap};
|
||||
use std::{fs, path::PathBuf, sync::Mutex};
|
||||
|
||||
use crate::{Config, FONT_SYSTEM, SYNTAX_SYSTEM};
|
||||
|
||||
static FONT_SIZES: &'static [Metrics] = &[
|
||||
Metrics::new(10.0, 14.0), // Caption
|
||||
Metrics::new(14.0, 20.0), // Body
|
||||
Metrics::new(20.0, 28.0), // Title 4
|
||||
Metrics::new(24.0, 32.0), // Title 3
|
||||
Metrics::new(28.0, 36.0), // Title 2
|
||||
Metrics::new(32.0, 44.0), // Title 1
|
||||
];
|
||||
|
||||
pub struct Tab {
|
||||
pub path_opt: Option<PathBuf>,
|
||||
attrs: Attrs<'static>,
|
||||
pub editor: Mutex<ViEditor<'static>>,
|
||||
}
|
||||
|
||||
impl Tab {
|
||||
pub fn new() -> Self {
|
||||
let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace);
|
||||
|
||||
let editor = SyntaxEditor::new(
|
||||
Buffer::new(&mut FONT_SYSTEM.lock().unwrap(), FONT_SIZES[1 /* Body */]),
|
||||
&SYNTAX_SYSTEM,
|
||||
"base16-eighties.dark",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut editor = ViEditor::new(editor);
|
||||
editor.set_passthrough(false);
|
||||
|
||||
Self {
|
||||
path_opt: None,
|
||||
attrs,
|
||||
editor: Mutex::new(editor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_config(&mut self, config: &Config) {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
||||
let mut editor = editor.borrow_with(&mut font_system);
|
||||
editor
|
||||
.buffer_mut()
|
||||
.set_wrap(if config.wrap { Wrap::Word } else { Wrap::None });
|
||||
}
|
||||
|
||||
pub fn open(&mut self, path: PathBuf) {
|
||||
let mut editor = self.editor.lock().unwrap();
|
||||
let mut font_system = FONT_SYSTEM.lock().unwrap();
|
||||
let mut editor = editor.borrow_with(&mut font_system);
|
||||
match editor.load_text(&path, self.attrs) {
|
||||
Ok(()) => {
|
||||
log::info!("opened {:?}", path);
|
||||
self.path_opt = match fs::canonicalize(&path) {
|
||||
Ok(ok) => Some(ok),
|
||||
Err(err) => {
|
||||
log::error!("failed to canonicalize {:?}: {}", path, err);
|
||||
Some(path)
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to open {:?}: {}", path, err);
|
||||
self.path_opt = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
if let Some(path) = &self.path_opt {
|
||||
let editor = self.editor.lock().unwrap();
|
||||
let mut text = String::new();
|
||||
for line in editor.buffer().lines.iter() {
|
||||
text.push_str(line.text());
|
||||
text.push('\n');
|
||||
}
|
||||
match fs::write(path, text) {
|
||||
Ok(()) => {
|
||||
log::info!("saved {:?}", path);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("failed to save {:?}: {}", path, err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::warn!("tab has no path yet");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self) -> String {
|
||||
//TODO: show full title when there is a conflict
|
||||
if let Some(path) = &self.path_opt {
|
||||
match path.file_name() {
|
||||
Some(file_name_os) => match file_name_os.to_str() {
|
||||
Some(file_name) => file_name.to_string(),
|
||||
None => format!("{}", path.display()),
|
||||
},
|
||||
None => format!("{}", path.display()),
|
||||
}
|
||||
} else {
|
||||
"New document".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue