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]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.6"
|
version = "0.7.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -110,9 +110,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.4"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72832d73be48bac96a5d7944568f305d829ed55b0ce3b483647089dfaf6cf704"
|
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -303,7 +303,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"parking",
|
"parking",
|
||||||
"polling 2.8.0",
|
"polling 2.8.0",
|
||||||
"rustix 0.37.26",
|
"rustix 0.37.27",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2",
|
||||||
"waker-fn",
|
"waker-fn",
|
||||||
|
|
@ -331,7 +331,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"event-listener 3.0.0",
|
"event-listener 3.0.0",
|
||||||
"futures-lite",
|
"futures-lite",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -358,7 +358,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"slab",
|
"slab",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
|
|
@ -405,7 +405,7 @@ version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f4d45f362125ed144544e57b0ec6de8fd6a296d41a6252fc4a20c0cf12e9ed3a"
|
checksum = "f4d45f362125ed144544e57b0ec6de8fd6a296d41a6252fc4a20c0cf12e9ed3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
@ -624,7 +624,7 @@ dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"log",
|
"log",
|
||||||
"polling 3.2.0",
|
"polling 3.2.0",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
@ -636,7 +636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
|
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"calloop 0.12.3",
|
"calloop 0.12.3",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"wayland-backend 0.3.2",
|
"wayland-backend 0.3.2",
|
||||||
"wayland-client 0.31.1",
|
"wayland-client 0.31.1",
|
||||||
]
|
]
|
||||||
|
|
@ -839,7 +839,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-config"
|
name = "cosmic-config"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"atomicwrites",
|
"atomicwrites",
|
||||||
"cosmic-config-derive",
|
"cosmic-config-derive",
|
||||||
|
|
@ -853,7 +853,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-config-derive"
|
name = "cosmic-config-derive"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
|
|
@ -895,7 +895,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.10.0"
|
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 = [
|
dependencies = [
|
||||||
"fontdb 0.15.0",
|
"fontdb 0.15.0",
|
||||||
"libm",
|
"libm",
|
||||||
|
|
@ -916,7 +916,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-theme"
|
name = "cosmic-theme"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"almost",
|
"almost",
|
||||||
"cosmic-config",
|
"cosmic-config",
|
||||||
|
|
@ -929,9 +929,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.10"
|
version = "0.2.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4"
|
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
@ -1638,9 +1638,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
@ -1653,9 +1653,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
|
@ -1663,15 +1663,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
|
@ -1681,9 +1681,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-lite"
|
name = "futures-lite"
|
||||||
|
|
@ -1702,9 +1702,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -1713,21 +1713,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
@ -1986,7 +1986,7 @@ version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.7.6",
|
"ahash 0.7.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1995,7 +1995,7 @@ version = "0.14.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.4",
|
"ahash 0.8.6",
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2047,7 +2047,7 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced"
|
name = "iced"
|
||||||
version = "0.10.0"
|
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 = [
|
dependencies = [
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
|
@ -2063,7 +2063,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_accessibility"
|
name = "iced_accessibility"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"accesskit",
|
"accesskit",
|
||||||
"accesskit_unix",
|
"accesskit_unix",
|
||||||
|
|
@ -2073,7 +2073,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_core"
|
name = "iced_core"
|
||||||
version = "0.10.0"
|
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 = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
|
|
@ -2088,7 +2088,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_futures"
|
name = "iced_futures"
|
||||||
version = "0.7.0"
|
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 = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
|
@ -2100,7 +2100,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_graphics"
|
name = "iced_graphics"
|
||||||
version = "0.9.0"
|
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 = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -2118,7 +2118,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_renderer"
|
name = "iced_renderer"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"iced_graphics",
|
"iced_graphics",
|
||||||
"iced_tiny_skia",
|
"iced_tiny_skia",
|
||||||
|
|
@ -2131,7 +2131,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_runtime"
|
name = "iced_runtime"
|
||||||
version = "0.1.1"
|
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 = [
|
dependencies = [
|
||||||
"iced_accessibility",
|
"iced_accessibility",
|
||||||
"iced_core",
|
"iced_core",
|
||||||
|
|
@ -2143,7 +2143,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_sctk"
|
name = "iced_sctk"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"enum-repr",
|
"enum-repr",
|
||||||
"float-cmp",
|
"float-cmp",
|
||||||
|
|
@ -2167,7 +2167,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_style"
|
name = "iced_style"
|
||||||
version = "0.9.0"
|
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 = [
|
dependencies = [
|
||||||
"iced_core",
|
"iced_core",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -2177,7 +2177,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_tiny_skia"
|
name = "iced_tiny_skia"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cosmic-text 0.9.0",
|
"cosmic-text 0.9.0",
|
||||||
|
|
@ -2195,7 +2195,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_wgpu"
|
name = "iced_wgpu"
|
||||||
version = "0.11.1"
|
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 = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
|
@ -2217,7 +2217,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_widget"
|
name = "iced_widget"
|
||||||
version = "0.1.3"
|
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 = [
|
dependencies = [
|
||||||
"iced_renderer",
|
"iced_renderer",
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
|
|
@ -2232,7 +2232,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iced_winit"
|
name = "iced_winit"
|
||||||
version = "0.10.0"
|
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 = [
|
dependencies = [
|
||||||
"iced_graphics",
|
"iced_graphics",
|
||||||
"iced_runtime",
|
"iced_runtime",
|
||||||
|
|
@ -2357,7 +2357,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2479,7 +2479,7 @@ checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libcosmic"
|
name = "libcosmic"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"apply",
|
"apply",
|
||||||
"ashpd",
|
"ashpd",
|
||||||
|
|
@ -3483,7 +3483,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"concurrent-queue",
|
"concurrent-queue",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
@ -3841,9 +3841,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.37.26"
|
version = "0.37.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995"
|
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"errno",
|
"errno",
|
||||||
|
|
@ -3855,9 +3855,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.20"
|
version = "0.38.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0"
|
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"errno",
|
"errno",
|
||||||
|
|
@ -3991,18 +3991,18 @@ checksum = "4c309e515543e67811222dbc9e3dd7e1056279b782e1dacffe4242b718734fb6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.189"
|
version = "1.0.190"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
|
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.189"
|
version = "1.0.190"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
|
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -4375,14 +4375,14 @@ checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.8.0"
|
version = "3.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
|
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand 2.0.1",
|
"fastrand 2.0.1",
|
||||||
"redox_syscall 0.3.5",
|
"redox_syscall 0.4.1",
|
||||||
"rustix 0.38.20",
|
"rustix 0.38.21",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -4548,21 +4548,21 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.3"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b150d2f463da7b52f12110d3995dc86598bf90d535e929e5f5af15ab89155011"
|
checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_edit 0.20.3",
|
"toml_edit 0.20.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.4"
|
version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51cc078118ed25af325985ff674c00c8416b0f962be67da4946854ebfc99f334"
|
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
@ -4580,9 +4580,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.20.3"
|
version = "0.20.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a2534c1aa199edef7108fb7d970facaa17f8f8cc5ce6bde75372cfa1051ed91"
|
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.2",
|
"indexmap 2.0.2",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -5854,18 +5854,18 @@ checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.11"
|
version = "0.7.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c19fae0c8a9efc6a8281f2e623db8af1db9e57852e04cde3e754dd2dc29340f"
|
checksum = "a1808c6e5aa42cb2b67578276190258f08679c275d92a19735aad5d359e1b154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.7.11"
|
version = "0.7.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc56589e9ddd1f1c28d4b4b5c773ce232910a6bb67a70133d61c9e347585efe9"
|
checksum = "ea597733532c780ce3e110401fc3b359b984cc78e17ac0917d3f371ffd1c1618"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ features = ["syntect", "vi"]
|
||||||
|
|
||||||
[dependencies.libcosmic]
|
[dependencies.libcosmic]
|
||||||
git = "https://github.com/pop-os/libcosmic"
|
git = "https://github.com/pop-os/libcosmic"
|
||||||
|
branch = "menu"
|
||||||
default-features = false
|
default-features = false
|
||||||
|
#path = "../libcosmic"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wayland"]
|
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
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
app::{Command, Core, Settings},
|
app::{message, Command, Core, Settings},
|
||||||
executor,
|
executor,
|
||||||
iced::{
|
iced::{
|
||||||
widget::{column, row, text},
|
widget::{row, text},
|
||||||
Alignment, Length, Limits,
|
Alignment, Length, Limits,
|
||||||
},
|
},
|
||||||
widget::{self, icon, segmented_button, view_switcher},
|
style,
|
||||||
|
widget::{self, button, icon, nav_bar, segmented_button, view_switcher},
|
||||||
ApplicationExt, Element,
|
ApplicationExt, Element,
|
||||||
};
|
};
|
||||||
use cosmic_text::{
|
use cosmic_text::{FontSystem, SyntaxSystem, ViMode};
|
||||||
Attrs, Buffer, Edit, FontSystem, Metrics, SyntaxEditor, SyntaxSystem, ViEditor, ViMode,
|
use std::{
|
||||||
|
env, fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
use std::{env, fs, path::PathBuf, sync::Mutex};
|
|
||||||
|
|
||||||
use self::menu_list::MenuList;
|
use self::menu::menu_bar;
|
||||||
mod menu_list;
|
mod menu;
|
||||||
|
|
||||||
|
use self::project::ProjectNode;
|
||||||
|
mod project;
|
||||||
|
|
||||||
|
use self::tab::Tab;
|
||||||
|
mod tab;
|
||||||
|
|
||||||
use self::text_box::text_box;
|
use self::text_box::text_box;
|
||||||
mod text_box;
|
mod text_box;
|
||||||
|
|
||||||
|
//TODO: re-use iced FONT_SYSTEM
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref FONT_SYSTEM: Mutex<FontSystem> = Mutex::new(FontSystem::new());
|
static ref FONT_SYSTEM: Mutex<FontSystem> = Mutex::new(FontSystem::new());
|
||||||
static ref SYNTAX_SYSTEM: SyntaxSystem = SyntaxSystem::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>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tab {
|
#[derive(Clone, Debug)]
|
||||||
path_opt: Option<PathBuf>,
|
pub struct Config {
|
||||||
attrs: Attrs<'static>,
|
wrap: bool,
|
||||||
editor: Mutex<ViEditor<'static>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tab {
|
impl Config {
|
||||||
|
//TODO: load from cosmic-config
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace);
|
Self { wrap: false }
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
core: Core,
|
core: Core,
|
||||||
|
nav_model: segmented_button::SingleSelectModel,
|
||||||
tab_model: segmented_button::SingleSelectModel,
|
tab_model: segmented_button::SingleSelectModel,
|
||||||
|
config: Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
Open,
|
New,
|
||||||
|
OpenFileDialog,
|
||||||
|
OpenFile(PathBuf),
|
||||||
|
OpenProjectDialog,
|
||||||
|
OpenProject(PathBuf),
|
||||||
Save,
|
Save,
|
||||||
TabActivate(segmented_button::Entity),
|
TabActivate(segmented_button::Entity),
|
||||||
TabClose(segmented_button::Entity),
|
TabClose(segmented_button::Entity),
|
||||||
Todo,
|
Todo,
|
||||||
|
Wrap(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -149,25 +89,168 @@ impl App {
|
||||||
self.tab_model.active_data_mut()
|
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>) {
|
pub fn open_tab(&mut self, path_opt: Option<PathBuf>) {
|
||||||
let mut tab = Tab::new();
|
let mut tab = Tab::new();
|
||||||
|
tab.set_config(&self.config);
|
||||||
if let Some(path) = path_opt {
|
if let Some(path) = path_opt {
|
||||||
tab.open(path);
|
tab.open(path);
|
||||||
}
|
}
|
||||||
self.tab_model
|
self.tab_model
|
||||||
.insert()
|
.insert()
|
||||||
.text(tab.title())
|
.text(tab.title())
|
||||||
.icon(icon::from_name("text-x-generic").icon())
|
.icon(icon::from_name("text-x-generic").size(16).icon())
|
||||||
.data(tab)
|
.data::<Tab>(tab)
|
||||||
.closable()
|
.closable()
|
||||||
.activate();
|
.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> {
|
pub fn update_title(&mut self) -> Command<Message> {
|
||||||
|
self.update_nav_bar_active();
|
||||||
|
|
||||||
let title = match self.active_tab() {
|
let title = match self.active_tab() {
|
||||||
Some(tab) => tab.title(),
|
Some(tab) => tab.title(),
|
||||||
None => format!("No Open File"),
|
None => format!("No Open File"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let window_title = format!("{title} - COSMIC Text Editor");
|
let window_title = format!("{title} - COSMIC Text Editor");
|
||||||
self.set_header_title(title.clone());
|
self.set_header_title(title.clone());
|
||||||
self.set_window_title(window_title)
|
self.set_window_title(window_title)
|
||||||
|
|
@ -186,7 +269,7 @@ impl cosmic::Application for App {
|
||||||
type Message = Message;
|
type Message = Message;
|
||||||
|
|
||||||
/// The unique application ID to supply to the window manager.
|
/// 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 {
|
fn core(&self) -> &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>) {
|
fn init(core: Core, _flags: Self::Flags) -> (Self, Command<Self::Message>) {
|
||||||
let mut app = App {
|
let mut app = App {
|
||||||
core,
|
core,
|
||||||
|
nav_model: nav_bar::Model::builder().build(),
|
||||||
tab_model: segmented_button::Model::builder().build(),
|
tab_model: segmented_button::Model::builder().build(),
|
||||||
|
config: Config::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for path in env::args().skip(1) {
|
for arg in env::args().skip(1) {
|
||||||
app.open_tab(Some(PathBuf::from(path)));
|
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
|
// Open an empty file if no arguments provided
|
||||||
|
|
@ -216,13 +315,104 @@ impl cosmic::Application for App {
|
||||||
(app, command)
|
(app, command)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Self::Message> {
|
fn nav_model(&self) -> Option<&nav_bar::Model> {
|
||||||
match message {
|
Some(&self.nav_model)
|
||||||
Message::Open => {
|
}
|
||||||
if let Some(path) = rfd::FileDialog::new().pick_file() {
|
|
||||||
self.open_tab(Some(path));
|
fn on_nav_select(&mut self, id: nav_bar::Id) -> Command<Message> {
|
||||||
return self.update_title();
|
// 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 => {
|
Message::Save => {
|
||||||
let mut title_opt = None;
|
let mut title_opt = None;
|
||||||
|
|
@ -230,6 +420,7 @@ impl cosmic::Application for App {
|
||||||
match self.active_tab_mut() {
|
match self.active_tab_mut() {
|
||||||
Some(tab) => {
|
Some(tab) => {
|
||||||
if tab.path_opt.is_none() {
|
if tab.path_opt.is_none() {
|
||||||
|
//TODO: use async file dialog
|
||||||
tab.path_opt = rfd::FileDialog::new().save_file();
|
tab.path_opt = rfd::FileDialog::new().save_file();
|
||||||
title_opt = Some(tab.title());
|
title_opt = Some(tab.title());
|
||||||
}
|
}
|
||||||
|
|
@ -271,37 +462,40 @@ impl cosmic::Application for App {
|
||||||
Message::Todo => {
|
Message::Todo => {
|
||||||
log::warn!("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()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn view(&self) -> Element<Message> {
|
fn header_start(&self) -> Vec<Element<Message>> {
|
||||||
let menu_bar = row![
|
vec![menu_bar(&self.config)]
|
||||||
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 view(&self) -> Element<Message> {
|
||||||
let mut tab_column = widget::column::with_capacity(3).padding([0, 16]);
|
let mut tab_column = widget::column::with_capacity(3).padding([0, 16]);
|
||||||
|
|
||||||
tab_column = tab_column.push(
|
tab_column = tab_column.push(
|
||||||
view_switcher::horizontal(&self.tab_model)
|
row![
|
||||||
.on_activate(Message::TabActivate)
|
view_switcher::horizontal(&self.tab_model)
|
||||||
.on_close(Message::TabClose)
|
.on_activate(Message::TabActivate)
|
||||||
.width(Length::Shrink),
|
.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() {
|
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:
|
// Uncomment to debug layout:
|
||||||
//content.explain(Color::WHITE)
|
//content.explain(cosmic::iced::Color::WHITE)
|
||||||
content
|
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