feat: settings application architecture
This commit is contained in:
commit
2709dcfee5
43 changed files with 7244 additions and 0 deletions
41
.github/workflows/ci.yml
vendored
Normal file
41
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: Continuous integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
fmt:
|
||||
name: Rustfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: install toolchain
|
||||
run: rustup toolchain install stable --component rustfmt
|
||||
- name: fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
clippy:
|
||||
name: Clippy Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: install system dependencies
|
||||
run: sudo apt-get update && sudo apt-get install cmake libexpat1-dev libfontconfig-dev libfreetype-dev pkg-config
|
||||
- uses: actions/checkout@v3
|
||||
- name: install toolchain
|
||||
run: rustup show
|
||||
- name: clippy
|
||||
run: cargo clippy --all-features
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: install system dependencies
|
||||
run: sudo apt-get update && sudo apt-get install cmake libexpat1-dev libfontconfig-dev libfreetype-dev pkg-config
|
||||
- uses: actions/checkout@v3
|
||||
- name: install toolchain
|
||||
run: rustup show
|
||||
- name: test
|
||||
run: cargo test --all-features
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
target
|
||||
vendor
|
||||
vendor.tar
|
||||
.cargo
|
||||
debian/*
|
||||
!debian/changelog
|
||||
!debian/control
|
||||
!debian/copyright
|
||||
!debian/rules
|
||||
!debian/source
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.check.overrideCommand": ["just", "check-json"]
|
||||
}
|
||||
4116
Cargo.lock
generated
Normal file
4116
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
25
Cargo.toml
Normal file
25
Cargo.toml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "cosmic-settings"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-only"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[dependencies]
|
||||
apply = "0.3.0"
|
||||
derive_setters = "0.1.5"
|
||||
i18n-embed-fl = "0.6.5"
|
||||
once_cell = "1.17.0"
|
||||
regex = "1.7.1"
|
||||
rust-embed = "6.4.2"
|
||||
slotmap = "1.0.6"
|
||||
|
||||
[dependencies.i18n-embed]
|
||||
version = "0.13.8"
|
||||
features = ["fluent-system", "desktop-requester"]
|
||||
|
||||
[dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
branch = "settings-improv"
|
||||
default-features = false
|
||||
features = ["debug", "winit", "dyrend"]
|
||||
595
LICENSE.md
Normal file
595
LICENSE.md
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
GNU General Public License
|
||||
==========================
|
||||
|
||||
_Version 3, 29 June 2007_
|
||||
_Copyright © 2007 Free Software Foundation, Inc. <<http://fsf.org/>>_
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license
|
||||
document, but changing it is not allowed.
|
||||
|
||||
## Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for software and other
|
||||
kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away
|
||||
your freedom to share and change the works. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change all versions of a
|
||||
program--to make sure it remains free software for all its users. We, the Free
|
||||
Software Foundation, use the GNU General Public License for most of our software; it
|
||||
applies also to any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General
|
||||
Public Licenses are designed to make sure that you have the freedom to distribute
|
||||
copies of free software (and charge for them if you wish), that you receive source
|
||||
code or can get it if you want it, that you can change the software or use pieces of
|
||||
it in new free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you these rights or
|
||||
asking you to surrender the rights. Therefore, you have certain responsibilities if
|
||||
you distribute copies of the software, or if you modify it: responsibilities to
|
||||
respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or for a fee,
|
||||
you must pass on to the recipients the same freedoms that you received. You must make
|
||||
sure that they, too, receive or can get the source code. And you must show them these
|
||||
terms so they know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps: **(1)** assert
|
||||
copyright on the software, and **(2)** offer you this License giving you legal permission
|
||||
to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains that there is
|
||||
no warranty for this free software. For both users' and authors' sake, the GPL
|
||||
requires that modified versions be marked as changed, so that their problems will not
|
||||
be attributed erroneously to authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run modified versions of
|
||||
the software inside them, although the manufacturer can do so. This is fundamentally
|
||||
incompatible with the aim of protecting users' freedom to change the software. The
|
||||
systematic pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we have designed
|
||||
this version of the GPL to prohibit the practice for those products. If such problems
|
||||
arise substantially in other domains, we stand ready to extend this provision to
|
||||
those domains in future versions of the GPL, as needed to protect the freedom of
|
||||
users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents. States should
|
||||
not allow patents to restrict development and use of software on general-purpose
|
||||
computers, but in those that do, we wish to avoid the special danger that patents
|
||||
applied to a free program could make it effectively proprietary. To prevent this, the
|
||||
GPL assures that patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
## TERMS AND CONDITIONS
|
||||
|
||||
### 0. Definitions
|
||||
|
||||
“This License” refers to version 3 of the GNU General Public License.
|
||||
|
||||
“Copyright” also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
“The Program” refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as “you”. “Licensees” and
|
||||
“recipients” may be individuals or organizations.
|
||||
|
||||
To “modify” a work means to copy from or adapt all or part of the work in
|
||||
a fashion requiring copyright permission, other than the making of an exact copy. The
|
||||
resulting work is called a “modified version” of the earlier work or a
|
||||
work “based on” the earlier work.
|
||||
|
||||
A “covered work” means either the unmodified Program or a work based on
|
||||
the Program.
|
||||
|
||||
To “propagate” a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for infringement under
|
||||
applicable copyright law, except executing it on a computer or modifying a private
|
||||
copy. Propagation includes copying, distribution (with or without modification),
|
||||
making available to the public, and in some countries other activities as well.
|
||||
|
||||
To “convey” a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through a computer
|
||||
network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays “Appropriate Legal Notices” to the
|
||||
extent that it includes a convenient and prominently visible feature that **(1)**
|
||||
displays an appropriate copyright notice, and **(2)** tells the user that there is no
|
||||
warranty for the work (except to the extent that warranties are provided), that
|
||||
licensees may convey the work under this License, and how to view a copy of this
|
||||
License. If the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
### 1. Source Code
|
||||
|
||||
The “source code” for a work means the preferred form of the work for
|
||||
making modifications to it. “Object code” means any non-source form of a
|
||||
work.
|
||||
|
||||
A “Standard Interface” means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of interfaces
|
||||
specified for a particular programming language, one that is widely used among
|
||||
developers working in that language.
|
||||
|
||||
The “System Libraries” of an executable work include anything, other than
|
||||
the work as a whole, that **(a)** is included in the normal form of packaging a Major
|
||||
Component, but which is not part of that Major Component, and **(b)** serves only to
|
||||
enable use of the work with that Major Component, or to implement a Standard
|
||||
Interface for which an implementation is available to the public in source code form.
|
||||
A “Major Component”, in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system (if any) on which
|
||||
the executable work runs, or a compiler used to produce the work, or an object code
|
||||
interpreter used to run it.
|
||||
|
||||
The “Corresponding Source” for a work in object code form means all the
|
||||
source code needed to generate, install, and (for an executable work) run the object
|
||||
code and to modify the work, including scripts to control those activities. However,
|
||||
it does not include the work's System Libraries, or general-purpose tools or
|
||||
generally available free programs which are used unmodified in performing those
|
||||
activities but which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for the work, and
|
||||
the source code for shared libraries and dynamically linked subprograms that the work
|
||||
is specifically designed to require, such as by intimate data communication or
|
||||
control flow between those subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can regenerate
|
||||
automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
|
||||
### 2. Basic Permissions
|
||||
|
||||
All rights granted under this License are granted for the term of copyright on the
|
||||
Program, and are irrevocable provided the stated conditions are met. This License
|
||||
explicitly affirms your unlimited permission to run the unmodified Program. The
|
||||
output from running a covered work is covered by this License only if the output,
|
||||
given its content, constitutes a covered work. This License acknowledges your rights
|
||||
of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey, without
|
||||
conditions so long as your license otherwise remains in force. You may convey covered
|
||||
works to others for the sole purpose of having them make modifications exclusively
|
||||
for you, or provide you with facilities for running those works, provided that you
|
||||
comply with the terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for you must do so
|
||||
exclusively on your behalf, under your direction and control, on terms that prohibit
|
||||
them from making any copies of your copyrighted material outside their relationship
|
||||
with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the conditions
|
||||
stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||
|
||||
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
|
||||
|
||||
No covered work shall be deemed part of an effective technological measure under any
|
||||
applicable law fulfilling obligations under article 11 of the WIPO copyright treaty
|
||||
adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention
|
||||
of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid circumvention of
|
||||
technological measures to the extent such circumvention is effected by exercising
|
||||
rights under this License with respect to the covered work, and you disclaim any
|
||||
intention to limit operation or modification of the work as a means of enforcing,
|
||||
against the work's users, your or third parties' legal rights to forbid circumvention
|
||||
of technological measures.
|
||||
|
||||
### 4. Conveying Verbatim Copies
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you receive it, in any
|
||||
medium, provided that you conspicuously and appropriately publish on each copy an
|
||||
appropriate copyright notice; keep intact all notices stating that this License and
|
||||
any non-permissive terms added in accord with section 7 apply to the code; keep
|
||||
intact all notices of the absence of any warranty; and give all recipients a copy of
|
||||
this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey, and you may offer
|
||||
support or warranty protection for a fee.
|
||||
|
||||
### 5. Conveying Modified Source Versions
|
||||
|
||||
You may convey a work based on the Program, or the modifications to produce it from
|
||||
the Program, in the form of source code under the terms of section 4, provided that
|
||||
you also meet all of these conditions:
|
||||
|
||||
* **a)** The work must carry prominent notices stating that you modified it, and giving a
|
||||
relevant date.
|
||||
* **b)** The work must carry prominent notices stating that it is released under this
|
||||
License and any conditions added under section 7. This requirement modifies the
|
||||
requirement in section 4 to “keep intact all notices”.
|
||||
* **c)** You must license the entire work, as a whole, under this License to anyone who
|
||||
comes into possession of a copy. This License will therefore apply, along with any
|
||||
applicable section 7 additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no permission to license the
|
||||
work in any other way, but it does not invalidate such permission if you have
|
||||
separately received it.
|
||||
* **d)** If the work has interactive user interfaces, each must display Appropriate Legal
|
||||
Notices; however, if the Program has interactive interfaces that do not display
|
||||
Appropriate Legal Notices, your work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent works, which are
|
||||
not by their nature extensions of the covered work, and which are not combined with
|
||||
it such as to form a larger program, in or on a volume of a storage or distribution
|
||||
medium, is called an “aggregate” if the compilation and its resulting
|
||||
copyright are not used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work in an aggregate
|
||||
does not cause this License to apply to the other parts of the aggregate.
|
||||
|
||||
### 6. Conveying Non-Source Forms
|
||||
|
||||
You may convey a covered work in object code form under the terms of sections 4 and
|
||||
5, provided that you also convey the machine-readable Corresponding Source under the
|
||||
terms of this License, in one of these ways:
|
||||
|
||||
* **a)** Convey the object code in, or embodied in, a physical product (including a
|
||||
physical distribution medium), accompanied by the Corresponding Source fixed on a
|
||||
durable physical medium customarily used for software interchange.
|
||||
* **b)** Convey the object code in, or embodied in, a physical product (including a
|
||||
physical distribution medium), accompanied by a written offer, valid for at least
|
||||
three years and valid for as long as you offer spare parts or customer support for
|
||||
that product model, to give anyone who possesses the object code either **(1)** a copy of
|
||||
the Corresponding Source for all the software in the product that is covered by this
|
||||
License, on a durable physical medium customarily used for software interchange, for
|
||||
a price no more than your reasonable cost of physically performing this conveying of
|
||||
source, or **(2)** access to copy the Corresponding Source from a network server at no
|
||||
charge.
|
||||
* **c)** Convey individual copies of the object code with a copy of the written offer to
|
||||
provide the Corresponding Source. This alternative is allowed only occasionally and
|
||||
noncommercially, and only if you received the object code with such an offer, in
|
||||
accord with subsection 6b.
|
||||
* **d)** Convey the object code by offering access from a designated place (gratis or for
|
||||
a charge), and offer equivalent access to the Corresponding Source in the same way
|
||||
through the same place at no further charge. You need not require recipients to copy
|
||||
the Corresponding Source along with the object code. If the place to copy the object
|
||||
code is a network server, the Corresponding Source may be on a different server
|
||||
(operated by you or a third party) that supports equivalent copying facilities,
|
||||
provided you maintain clear directions next to the object code saying where to find
|
||||
the Corresponding Source. Regardless of what server hosts the Corresponding Source,
|
||||
you remain obligated to ensure that it is available for as long as needed to satisfy
|
||||
these requirements.
|
||||
* **e)** Convey the object code using peer-to-peer transmission, provided you inform
|
||||
other peers where the object code and Corresponding Source of the work are being
|
||||
offered to the general public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded from the
|
||||
Corresponding Source as a System Library, need not be included in conveying the
|
||||
object code work.
|
||||
|
||||
A “User Product” is either **(1)** a “consumer product”, which
|
||||
means any tangible personal property which is normally used for personal, family, or
|
||||
household purposes, or **(2)** anything designed or sold for incorporation into a
|
||||
dwelling. In determining whether a product is a consumer product, doubtful cases
|
||||
shall be resolved in favor of coverage. For a particular product received by a
|
||||
particular user, “normally used” refers to a typical or common use of
|
||||
that class of product, regardless of the status of the particular user or of the way
|
||||
in which the particular user actually uses, or expects or is expected to use, the
|
||||
product. A product is a consumer product regardless of whether the product has
|
||||
substantial commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
“Installation Information” for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install and execute
|
||||
modified versions of a covered work in that User Product from a modified version of
|
||||
its Corresponding Source. The information must suffice to ensure that the continued
|
||||
functioning of the modified object code is in no case prevented or interfered with
|
||||
solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or specifically for
|
||||
use in, a User Product, and the conveying occurs as part of a transaction in which
|
||||
the right of possession and use of the User Product is transferred to the recipient
|
||||
in perpetuity or for a fixed term (regardless of how the transaction is
|
||||
characterized), the Corresponding Source conveyed under this section must be
|
||||
accompanied by the Installation Information. But this requirement does not apply if
|
||||
neither you nor any third party retains the ability to install modified object code
|
||||
on the User Product (for example, the work has been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a requirement to
|
||||
continue to provide support service, warranty, or updates for a work that has been
|
||||
modified or installed by the recipient, or for the User Product in which it has been
|
||||
modified or installed. Access to a network may be denied when the modification itself
|
||||
materially and adversely affects the operation of the network or violates the rules
|
||||
and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in accord with
|
||||
this section must be in a format that is publicly documented (and with an
|
||||
implementation available to the public in source code form), and must require no
|
||||
special password or key for unpacking, reading or copying.
|
||||
|
||||
### 7. Additional Terms
|
||||
|
||||
“Additional permissions” are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions. Additional
|
||||
permissions that are applicable to the entire Program shall be treated as though they
|
||||
were included in this License, to the extent that they are valid under applicable
|
||||
law. If additional permissions apply only to part of the Program, that part may be
|
||||
used separately under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option remove any
|
||||
additional permissions from that copy, or from any part of it. (Additional
|
||||
permissions may be written to require their own removal in certain cases when you
|
||||
modify the work.) You may place additional permissions on material, added by you to a
|
||||
covered work, for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add to a
|
||||
covered work, you may (if authorized by the copyright holders of that material)
|
||||
supplement the terms of this License with terms:
|
||||
|
||||
* **a)** Disclaiming warranty or limiting liability differently from the terms of
|
||||
sections 15 and 16 of this License; or
|
||||
* **b)** Requiring preservation of specified reasonable legal notices or author
|
||||
attributions in that material or in the Appropriate Legal Notices displayed by works
|
||||
containing it; or
|
||||
* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that
|
||||
modified versions of such material be marked in reasonable ways as different from the
|
||||
original version; or
|
||||
* **d)** Limiting the use for publicity purposes of names of licensors or authors of the
|
||||
material; or
|
||||
* **e)** Declining to grant rights under trademark law for use of some trade names,
|
||||
trademarks, or service marks; or
|
||||
* **f)** Requiring indemnification of licensors and authors of that material by anyone
|
||||
who conveys the material (or modified versions of it) with contractual assumptions of
|
||||
liability to the recipient, for any liability that these contractual assumptions
|
||||
directly impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered “further
|
||||
restrictions” within the meaning of section 10. If the Program as you received
|
||||
it, or any part of it, contains a notice stating that it is governed by this License
|
||||
along with a term that is a further restriction, you may remove that term. If a
|
||||
license document contains a further restriction but permits relicensing or conveying
|
||||
under this License, you may add to a covered work material governed by the terms of
|
||||
that license document, provided that the further restriction does not survive such
|
||||
relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must place, in
|
||||
the relevant source files, a statement of the additional terms that apply to those
|
||||
files, or a notice indicating where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the form of a
|
||||
separately written license, or stated as exceptions; the above requirements apply
|
||||
either way.
|
||||
|
||||
### 8. Termination
|
||||
|
||||
You may not propagate or modify a covered work except as expressly provided under
|
||||
this License. Any attempt otherwise to propagate or modify it is void, and will
|
||||
automatically terminate your rights under this License (including any patent licenses
|
||||
granted under the third paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license from a
|
||||
particular copyright holder is reinstated **(a)** provisionally, unless and until the
|
||||
copyright holder explicitly and finally terminates your license, and **(b)** permanently,
|
||||
if the copyright holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated permanently
|
||||
if the copyright holder notifies you of the violation by some reasonable means, this
|
||||
is the first time you have received notice of violation of this License (for any
|
||||
work) from that copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the licenses of
|
||||
parties who have received copies or rights from you under this License. If your
|
||||
rights have been terminated and not permanently reinstated, you do not qualify to
|
||||
receive new licenses for the same material under section 10.
|
||||
|
||||
### 9. Acceptance Not Required for Having Copies
|
||||
|
||||
You are not required to accept this License in order to receive or run a copy of the
|
||||
Program. Ancillary propagation of a covered work occurring solely as a consequence of
|
||||
using peer-to-peer transmission to receive a copy likewise does not require
|
||||
acceptance. However, nothing other than this License grants you permission to
|
||||
propagate or modify any covered work. These actions infringe copyright if you do not
|
||||
accept this License. Therefore, by modifying or propagating a covered work, you
|
||||
indicate your acceptance of this License to do so.
|
||||
|
||||
### 10. Automatic Licensing of Downstream Recipients
|
||||
|
||||
Each time you convey a covered work, the recipient automatically receives a license
|
||||
from the original licensors, to run, modify and propagate that work, subject to this
|
||||
License. You are not responsible for enforcing compliance by third parties with this
|
||||
License.
|
||||
|
||||
An “entity transaction” is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an organization, or
|
||||
merging organizations. If propagation of a covered work results from an entity
|
||||
transaction, each party to that transaction who receives a copy of the work also
|
||||
receives whatever licenses to the work the party's predecessor in interest had or
|
||||
could give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if the predecessor
|
||||
has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights granted or
|
||||
affirmed under this License. For example, you may not impose a license fee, royalty,
|
||||
or other charge for exercise of rights granted under this License, and you may not
|
||||
initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that any patent claim is infringed by making, using, selling, offering for sale, or
|
||||
importing the Program or any portion of it.
|
||||
|
||||
### 11. Patents
|
||||
|
||||
A “contributor” is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The work thus
|
||||
licensed is called the contributor's “contributor version”.
|
||||
|
||||
A contributor's “essential patent claims” are all patent claims owned or
|
||||
controlled by the contributor, whether already acquired or hereafter acquired, that
|
||||
would be infringed by some manner, permitted by this License, of making, using, or
|
||||
selling its contributor version, but do not include claims that would be infringed
|
||||
only as a consequence of further modification of the contributor version. For
|
||||
purposes of this definition, “control” includes the right to grant patent
|
||||
sublicenses in a manner consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license
|
||||
under the contributor's essential patent claims, to make, use, sell, offer for sale,
|
||||
import and otherwise run, modify and propagate the contents of its contributor
|
||||
version.
|
||||
|
||||
In the following three paragraphs, a “patent license” is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent (such as an
|
||||
express permission to practice a patent or covenant not to sue for patent
|
||||
infringement). To “grant” such a patent license to a party means to make
|
||||
such an agreement or commitment not to enforce a patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and the
|
||||
Corresponding Source of the work is not available for anyone to copy, free of charge
|
||||
and under the terms of this License, through a publicly available network server or
|
||||
other readily accessible means, then you must either **(1)** cause the Corresponding
|
||||
Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or **(3)** arrange, in a manner consistent with
|
||||
the requirements of this License, to extend the patent license to downstream
|
||||
recipients. “Knowingly relying” means you have actual knowledge that, but
|
||||
for the patent license, your conveying the covered work in a country, or your
|
||||
recipient's use of the covered work in a country, would infringe one or more
|
||||
identifiable patents in that country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or arrangement, you
|
||||
convey, or propagate by procuring conveyance of, a covered work, and grant a patent
|
||||
license to some of the parties receiving the covered work authorizing them to use,
|
||||
propagate, modify or convey a specific copy of the covered work, then the patent
|
||||
license you grant is automatically extended to all recipients of the covered work and
|
||||
works based on it.
|
||||
|
||||
A patent license is “discriminatory” if it does not include within the
|
||||
scope of its coverage, prohibits the exercise of, or is conditioned on the
|
||||
non-exercise of one or more of the rights that are specifically granted under this
|
||||
License. You may not convey a covered work if you are a party to an arrangement with
|
||||
a third party that is in the business of distributing software, under which you make
|
||||
payment to the third party based on the extent of your activity of conveying the
|
||||
work, and under which the third party grants, to any of the parties who would receive
|
||||
the covered work from you, a discriminatory patent license **(a)** in connection with
|
||||
copies of the covered work conveyed by you (or copies made from those copies), or **(b)**
|
||||
primarily for and in connection with specific products or compilations that contain
|
||||
the covered work, unless you entered into that arrangement, or that patent license
|
||||
was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any implied
|
||||
license or other defenses to infringement that may otherwise be available to you
|
||||
under applicable patent law.
|
||||
|
||||
### 12. No Surrender of Others' Freedom
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or otherwise)
|
||||
that contradict the conditions of this License, they do not excuse you from the
|
||||
conditions of this License. If you cannot convey a covered work so as to satisfy
|
||||
simultaneously your obligations under this License and any other pertinent
|
||||
obligations, then as a consequence you may not convey it at all. For example, if you
|
||||
agree to terms that obligate you to collect a royalty for further conveying from
|
||||
those to whom you convey the Program, the only way you could satisfy both those terms
|
||||
and this License would be to refrain entirely from conveying the Program.
|
||||
|
||||
### 13. Use with the GNU Affero General Public License
|
||||
|
||||
Notwithstanding any other provision of this License, you have permission to link or
|
||||
combine any covered work with a work licensed under version 3 of the GNU Affero
|
||||
General Public License into a single combined work, and to convey the resulting work.
|
||||
The terms of this License will continue to apply to the part which is the covered
|
||||
work, but the special requirements of the GNU Affero General Public License, section
|
||||
13, concerning interaction through a network will apply to the combination as such.
|
||||
|
||||
### 14. Revised Versions of this License
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU
|
||||
General Public License from time to time. Such new versions will be similar in spirit
|
||||
to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies that
|
||||
a certain numbered version of the GNU General Public License “or any later
|
||||
version” applies to it, you have the option of following the terms and
|
||||
conditions either of that numbered version or of any later version published by the
|
||||
Free Software Foundation. If the Program does not specify a version number of the GNU
|
||||
General Public License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of the GNU
|
||||
General Public License can be used, that proxy's public statement of acceptance of a
|
||||
version permanently authorizes you to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions. However, no
|
||||
additional obligations are imposed on any author or copyright holder as a result of
|
||||
your choosing to follow a later version.
|
||||
|
||||
### 15. Disclaimer of Warranty
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
|
||||
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
### 16. Limitation of Liability
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
|
||||
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
|
||||
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
|
||||
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
||||
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
|
||||
OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
|
||||
WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
### 17. Interpretation of Sections 15 and 16
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided above cannot be
|
||||
given local legal effect according to their terms, reviewing courts shall apply local
|
||||
law that most closely approximates an absolute waiver of all civil liability in
|
||||
connection with the Program, unless a warranty or assumption of liability accompanies
|
||||
a copy of the Program in return for a fee.
|
||||
|
||||
_END OF TERMS AND CONDITIONS_
|
||||
|
||||
## How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible use to
|
||||
the public, the best way to achieve this is to make it free software which everyone
|
||||
can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them
|
||||
to the start of each source file to most effectively state the exclusion of warranty;
|
||||
and each file should have at least the “copyright” line and a pointer to
|
||||
where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type 'show c' for details.
|
||||
|
||||
The hypothetical commands `show w` and `show c` should show the appropriate parts of
|
||||
the General Public License. Of course, your program's commands might be different;
|
||||
for a GUI interface, you would use an “about box”.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school, if any, to
|
||||
sign a “copyright disclaimer” for the program, if necessary. For more
|
||||
information on this, and how to apply and follow the GNU GPL, see
|
||||
<<http://www.gnu.org/licenses/>>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may consider it
|
||||
more useful to permit linking proprietary applications with the library. If this is
|
||||
what you want to do, use the GNU Lesser General Public License instead of this
|
||||
License. But first, please read
|
||||
<<http://www.gnu.org/philosophy/why-not-lgpl.html>>.
|
||||
52
README.md
Normal file
52
README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# COSMIC Settings
|
||||
|
||||
> Prototype of a proof of concept that is an active work in progress.
|
||||
|
||||
The settings application for the [COSMIC desktop environment](https://github.com/pop-os/cosmic-epoch). Developed with [libcosmic](https://github.com/pop-os/libcosmic) in the [iced](https://iced.rs/) GUI library.
|
||||
|
||||
## Build
|
||||
|
||||
To compile, a stable Rust compiler and [just](https://github.com/casey/just) are required.
|
||||
|
||||
- cargo
|
||||
- just
|
||||
|
||||
Some C libraries are also required for font support at the moment.
|
||||
|
||||
- cmake
|
||||
- libexpat1-dev
|
||||
- libfontconfig-dev
|
||||
- libfreetype-dev
|
||||
- pkg-config
|
||||
|
||||
Then it can be compiled and installed like so.
|
||||
|
||||
```sh
|
||||
just build-release
|
||||
sudo just prefix=/usr install
|
||||
```
|
||||
|
||||
If you are packaging for Linux distribution, you can use the `rootdir` variable to change the root path, in addition to the prefix.
|
||||
|
||||
```sh
|
||||
just rootdir=debian/cosmic-settings prefix=/usr install
|
||||
```
|
||||
|
||||
## Translators
|
||||
|
||||
Translation files may be found in the [i18n directory](./i18n). New translations may copy the [English (en) localization](./i18n/en) of the project and rename `en` to the desired [ISO 639-1 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). Translations may be submitted through GitHub as an issue or pull request. Submissions by email or other means are also acceptable; with the preferred name and email to associate with the changes.
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the [GNU Public License 3.0](https://choosealicense.com/licenses/gpl-3.0).
|
||||
|
||||
|
||||
|
||||
### Contribution
|
||||
|
||||
Any contribution intentionally submitted for inclusion in the work by you shall be licensed under the GNU Public License 3.0 (GPL-3.0). Each source file should have a SPDX copyright notice at the top of the file:
|
||||
|
||||
```
|
||||
// Copyright {year-created} System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
```
|
||||
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
cosmic-settings (0.1.0) jammy; urgency=medium
|
||||
|
||||
* Project in development
|
||||
|
||||
-- Michael Murphy <michael@mmurphy.dev> Wed, 25 Jan 2023 16:27:38 +0100
|
||||
21
debian/control
vendored
Normal file
21
debian/control
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
Source: cosmic-settings
|
||||
Section: utils
|
||||
Priority: optional
|
||||
Maintainer: Michael Murphy <mmstick@pm.me>
|
||||
Build-Depends:
|
||||
debhelper-compat (=13),
|
||||
just,
|
||||
cargo,
|
||||
cmake,
|
||||
libexpat1-dev,
|
||||
libfontconfig-dev,
|
||||
libfreetype-dev,
|
||||
pkg-config,
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/pop-os/cosmic-settings
|
||||
|
||||
Package: cosmic-settings
|
||||
Architecture: amd64 arm64
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: Settings application for the COSMIC desktop environment
|
||||
Settings application for the COSMIC desktop environment
|
||||
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-settings
|
||||
Upstream-Contact: Michael Murphy <michael@mmurphy.dev>
|
||||
Source: https://github.com/pop-os/cosmic-settings
|
||||
Files: *
|
||||
Copyright: System76 <info@system76.com>
|
||||
License: GPL-3.0
|
||||
27
debian/rules
vendored
Executable file
27
debian/rules
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
export DESTDIR = debian/cosmic-settings
|
||||
export VENDOR ?= 1
|
||||
CLEAN ?= 1
|
||||
|
||||
%:
|
||||
dh $@
|
||||
|
||||
override_dh_auto_clean:
|
||||
if test "${CLEAN}" = "1"; then \
|
||||
cargo clean; \
|
||||
fi
|
||||
|
||||
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 x86-64-target=x86-64-v2 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)
|
||||
1
debian/source/options
vendored
Normal file
1
debian/source/options
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
tar-ignore = ".github .vscode vendor target"
|
||||
4
i18n.toml
Normal file
4
i18n.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
fallback_language = "en"
|
||||
|
||||
[fluent]
|
||||
assets_dir = "i18n"
|
||||
156
i18n/en/cosmic_settings.ftl
Normal file
156
i18n/en/cosmic_settings.ftl
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
app = COSMIC Settings
|
||||
|
||||
## Desktop
|
||||
|
||||
desktop = Desktop
|
||||
|
||||
## Desktop: Appearance
|
||||
|
||||
appearance = Appearance
|
||||
.desc = Accent colors and COSMIC theming.
|
||||
|
||||
## Desktop: Dock & Panel
|
||||
|
||||
dock = Dock & Top Panel
|
||||
.desc = Customize size, positions, and more for Dock and Top Panel.
|
||||
|
||||
## Desktop: Notifications
|
||||
|
||||
notifications = Notifications
|
||||
.desc = Do Not Disturb, lockscreen notifications, and per-application settings.
|
||||
|
||||
|
||||
## Desktop: Options
|
||||
|
||||
desktop-options = Desktop Options
|
||||
.desc = Super Key action, hot corners, window control options.
|
||||
|
||||
super-key-action = Super Key Action
|
||||
.launcher = Launcher
|
||||
.workspaces = Workspaces
|
||||
.applications = Applications
|
||||
|
||||
hot-corner = Hot Corner
|
||||
.top-left-corner = Enable top-left hot corner for Workspaces
|
||||
|
||||
top-panel = Top Panel
|
||||
.workspaces = Show Workspaces Button
|
||||
.applications = Show Applications Button
|
||||
|
||||
window-controls = Window Controls
|
||||
.minimize = Show Minimize Button
|
||||
.maximize = Show Maximize Button
|
||||
|
||||
## Desktop: Wallpaper
|
||||
|
||||
wallpaper = Wallpaper
|
||||
.desc = Background images, colors, and slideshow options.
|
||||
.same = Same background on all displays
|
||||
.fit = Background fit
|
||||
.slide = Slideshow
|
||||
.change = Change image every
|
||||
|
||||
|
||||
## Desktop: Workspaces
|
||||
|
||||
workspaces = Workspaces
|
||||
.desc = Set workspace number, behavior, and placement.
|
||||
|
||||
workspaces-behavior = Workspace Behavior
|
||||
.dynamic = Dynamic workspaces
|
||||
.fixed = Fixed Number of Workspaces
|
||||
|
||||
workspaces-multi-behavior = Multi-monitor Behavior
|
||||
.span = Workspaces Span Displays
|
||||
.separate = Displays Have Separate Workspaces
|
||||
|
||||
## Networking: Wired
|
||||
|
||||
wired = Wired
|
||||
.desc = Wired connection, connection profiles
|
||||
|
||||
## Networking: Online Accounts
|
||||
|
||||
online-accounts = Online Accounts
|
||||
.desc = Add accounts, IMAP and SMTP, enterprise logins
|
||||
|
||||
## Time & Language
|
||||
|
||||
time = Time & Language
|
||||
.desc = N/A
|
||||
|
||||
time-date = Date & Time
|
||||
.desc = Time zone, automatic clock settings, and some time formatting.
|
||||
.auto = Set automatically
|
||||
|
||||
time-zone = Time Zone
|
||||
.auto = Automatic time zone
|
||||
.auto-info = Requires location services and internet access
|
||||
|
||||
time-format = Date & Time Format
|
||||
.twenty-four = 24-hour time
|
||||
.first = First day of week
|
||||
|
||||
time-region = Region & Language
|
||||
.desc = Format dates, times, and numbers based on your region
|
||||
|
||||
## Sound
|
||||
|
||||
sound = Sound
|
||||
.desc = N/A
|
||||
|
||||
sound-output = Output
|
||||
.volume = Output volume
|
||||
.device = Output device
|
||||
.level = Output level
|
||||
.config = Configuration
|
||||
.balance = Balance
|
||||
|
||||
sound-input = Input
|
||||
.volume = Input volume
|
||||
.device = Input device
|
||||
.level = Input level
|
||||
|
||||
sound-alerts = Alerts
|
||||
.volume = Alerts volume
|
||||
.sound = Alerts sound
|
||||
|
||||
sound-applications = Applications
|
||||
.desc = Application volumes and settings
|
||||
|
||||
## System
|
||||
|
||||
system = System & Accounts
|
||||
|
||||
## System: About
|
||||
|
||||
about = About
|
||||
.desc = Device name, hardware information, operating system defaults.
|
||||
|
||||
about-device = Device name
|
||||
|
||||
about-hardware = Hardware
|
||||
.model = Hardware model
|
||||
.memory = Memory
|
||||
.processor = Processor
|
||||
.graphics = Graphics
|
||||
.disk-capacity = Disk Capacity
|
||||
|
||||
about-os = Operating System
|
||||
.os = Operating system
|
||||
.os-architecture = Operating system architecture
|
||||
.desktop-environment = Desktop environment
|
||||
.windowing-system = Windowing system
|
||||
|
||||
about-related = Related settings
|
||||
.support = Get support
|
||||
|
||||
## System: Firmware
|
||||
|
||||
firmware = Firmware
|
||||
.desc = Firmware details.
|
||||
|
||||
## System: Users
|
||||
|
||||
users = Users
|
||||
.desc = Authentication and login, lock screen.
|
||||
104
justfile
Normal file
104
justfile
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
name := 'cosmic-settings'
|
||||
appid := 'com.system76.CosmicSettings'
|
||||
|
||||
arch := `uname -m`
|
||||
|
||||
# Choose x86-64-v2, v3, or v4 for packaging
|
||||
x86-64-target := 'native'
|
||||
|
||||
export RUSTFLAGS := if arch == 'x86_64' {
|
||||
'-C target-cpu=' + x86-64-target
|
||||
} else {
|
||||
''
|
||||
}
|
||||
|
||||
rootdir := ''
|
||||
prefix := '/usr'
|
||||
|
||||
# File paths
|
||||
bin-src := 'target/release/' + name
|
||||
bin-dest := rootdir + prefix + '/bin/' + name
|
||||
|
||||
desktop := appid + '.desktop'
|
||||
desktop-src := 'resources/' + desktop
|
||||
desktop-dest := rootdir + prefix + '/share/applications/' + desktop
|
||||
|
||||
help:
|
||||
@echo '{{name}} ({{appid}})'
|
||||
@echo 'RUSTFLAGS={{RUSTFLAGS}}'
|
||||
@echo 'prefix={{prefix}}'
|
||||
@just -l
|
||||
|
||||
# Remove Cargo build artifacts
|
||||
clean:
|
||||
cargo clean
|
||||
|
||||
# Also remove .cargo and vendored dependencies
|
||||
clean-dist: clean
|
||||
rm -rf .cargo vendor vendor.tar target
|
||||
|
||||
# Run the application for testing purposes
|
||||
run:
|
||||
cargo run --release
|
||||
|
||||
# Compile debug build of cosmic-settings
|
||||
build-debug *args:
|
||||
cargo build {{args}}
|
||||
|
||||
# Compile release build of cosmic-settings
|
||||
build-release *args: (build-debug '--release' args)
|
||||
|
||||
# Vendored release build of cosmic-settings
|
||||
build-vendored *args: _vendor-extract (build-release '--frozen --offline' args)
|
||||
|
||||
# Run `cargo clippy`
|
||||
check *args:
|
||||
cargo clippy --all-features {{args}} -- -W clippy::pedantic
|
||||
|
||||
# Run `cargo clippy` with json message format
|
||||
check-json: (check '--message-format=json')
|
||||
|
||||
# Run `cargo test`
|
||||
test:
|
||||
cargo test --all-features
|
||||
|
||||
# Installation command
|
||||
_install options src dest:
|
||||
install {{options}} {{src}} {{dest}}
|
||||
|
||||
_install-bin src dest: (_install '-Dm0755' src dest)
|
||||
_install-file src dest: (_install '-Dm0644' src dest)
|
||||
|
||||
# Install everything
|
||||
install: (_install-bin bin-src bin-dest) (_install-file desktop-src desktop-dest)
|
||||
|
||||
# Uninstalls everything (requires same arguments as given to install)
|
||||
uninstall:
|
||||
rm -rf {{bin-dest}} {{desktop-dest}}
|
||||
|
||||
# Vendor Cargo 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 if vendor=1
|
||||
_vendor-extract:
|
||||
#!/usr/bin/env sh
|
||||
rm -rf vendor
|
||||
tar pxf vendor.tar
|
||||
|
||||
# Show the name of the project
|
||||
name:
|
||||
@cargo pkgid | sed -e 's:.*/::' -e 's:[#^].*::'
|
||||
|
||||
# Show the current version
|
||||
version:
|
||||
@cargo pkgid | sed -e 's:.*/::' -e 's:.*#::'
|
||||
|
||||
# Show the current git commit
|
||||
git-rev:
|
||||
@git rev-parse --short HEAD
|
||||
11
resources/com.system76.CosmicSettings.desktop
Normal file
11
resources/com.system76.CosmicSettings.desktop
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[Desktop Entry]
|
||||
Name=Cosmic Settings
|
||||
Type=Application
|
||||
Exec=cosmic-settings
|
||||
Terminal=false
|
||||
Categories=GNOME;GTK;
|
||||
Keywords=Gnome;GTK;
|
||||
OnlyShowIn=GNOME;Unity;COSMIC
|
||||
Icon=org.gnome.Settings
|
||||
StartupNotify=true
|
||||
NoDisplay=true
|
||||
1
rust-toolchain
Normal file
1
rust-toolchain
Normal file
|
|
@ -0,0 +1 @@
|
|||
stable
|
||||
443
src/app.rs
Normal file
443
src/app.rs
Normal file
|
|
@ -0,0 +1,443 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use apply::Apply;
|
||||
|
||||
use cosmic::{
|
||||
iced::widget::{self, column, container, horizontal_space, row},
|
||||
iced::{self, Application, Command, Length, Subscription},
|
||||
iced_native::{subscription, window},
|
||||
iced_winit::window::{close, drag, minimize, toggle_maximize},
|
||||
keyboard_nav,
|
||||
theme::Theme,
|
||||
widget::{
|
||||
header_bar, nav_bar, nav_bar_toggle, scrollable, search, segmented_button, settings,
|
||||
IconSource,
|
||||
},
|
||||
Element, ElementExt,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
page::{self, desktop, section, sound, system, time, Page},
|
||||
widget::{page_title, parent_page_button, search_header, sub_page_button},
|
||||
};
|
||||
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct SettingsApp {
|
||||
pub active_page: page::Entity,
|
||||
pub config: Config,
|
||||
pub debug: bool,
|
||||
pub is_condensed: bool,
|
||||
pub nav_bar_toggled_condensed: bool,
|
||||
pub nav_bar_toggled: bool,
|
||||
pub nav_bar: segmented_button::SingleSelectModel,
|
||||
pub pages: page::Model,
|
||||
pub scaling_factor: f32,
|
||||
pub search: search::Model,
|
||||
pub search_selections: Vec<(page::Entity, section::Entity)>,
|
||||
pub show_maximize: bool,
|
||||
pub show_minimize: bool,
|
||||
pub theme: Theme,
|
||||
pub title: String,
|
||||
pub window_width: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Config {
|
||||
nav_page: &'static str,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
Close,
|
||||
Desktop(desktop::Message),
|
||||
DateAndTime(time::date::Message),
|
||||
Drag,
|
||||
KeyboardNav(keyboard_nav::Message),
|
||||
Maximize,
|
||||
Minimize,
|
||||
NavBar(segmented_button::Entity),
|
||||
Search(search::Message),
|
||||
ToggleNavBar,
|
||||
ToggleNavBarCondensed,
|
||||
WindowResize(u32, u32),
|
||||
Page(page::Entity),
|
||||
}
|
||||
|
||||
impl Application for SettingsApp {
|
||||
type Executor = iced::executor::Default;
|
||||
type Flags = ();
|
||||
type Message = Message;
|
||||
type Theme = Theme;
|
||||
|
||||
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
|
||||
let mut app = SettingsApp {
|
||||
active_page: page::Entity::default(),
|
||||
config: Config {
|
||||
nav_page: "desktop",
|
||||
},
|
||||
debug: false,
|
||||
is_condensed: false,
|
||||
nav_bar: segmented_button::Model::default(),
|
||||
nav_bar_toggled: true,
|
||||
nav_bar_toggled_condensed: false,
|
||||
pages: page::Model::default(),
|
||||
title: crate::fl!("app"),
|
||||
scaling_factor: std::env::var("COSMIC_SCALE")
|
||||
.ok()
|
||||
.and_then(|scale| scale.parse::<f32>().ok())
|
||||
.unwrap_or(1.0),
|
||||
search: search::Model::default(),
|
||||
search_selections: Vec::default(),
|
||||
show_maximize: true,
|
||||
show_minimize: true,
|
||||
theme: Theme::Dark,
|
||||
window_width: 0,
|
||||
};
|
||||
|
||||
// app.insert_page::<wifi::Page>();
|
||||
// app.insert_page::<networking::Page>();
|
||||
// app.insert_page::<bluetooth::Page>();
|
||||
|
||||
app.insert_page::<desktop::Page>();
|
||||
|
||||
// app.insert_page::<input::Page>();
|
||||
|
||||
// app.insert_page::<displays::Page>();
|
||||
// app.insert_page::<power::Page>();
|
||||
|
||||
app.insert_page::<sound::Page>();
|
||||
|
||||
// app.insert_page::<printers::Page>();
|
||||
// app.insert_page::<privacy::Page>();
|
||||
|
||||
app.insert_page::<system::Page>();
|
||||
app.insert_page::<time::Page>();
|
||||
|
||||
// app.insert_page::<accessibility::Page>();
|
||||
// app.insert_page::<applications::Page>();
|
||||
|
||||
app.activate_navbar(app.active_page);
|
||||
|
||||
(app, Command::none())
|
||||
}
|
||||
|
||||
fn title(&self) -> String {
|
||||
self.title.clone()
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
let window_break = subscription::events_with(|event, _| match event {
|
||||
iced::Event::Window(_window_id, window::Event::Resized { width, height }) => {
|
||||
Some(Message::WindowResize(width, height))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
|
||||
Subscription::batch(vec![
|
||||
window_break,
|
||||
keyboard_nav::subscription().map(Message::KeyboardNav),
|
||||
])
|
||||
}
|
||||
|
||||
fn update(&mut self, message: Message) -> iced::Command<Self::Message> {
|
||||
let mut ret = Command::none();
|
||||
match message {
|
||||
Message::WindowResize(width, _height) => {
|
||||
let break_point = (600.0 * self.scaling_factor) as u32;
|
||||
self.window_width = width;
|
||||
self.is_condensed = self.window_width < break_point;
|
||||
}
|
||||
Message::KeyboardNav(message) => match message {
|
||||
keyboard_nav::Message::Unfocus => ret = keyboard_nav::unfocus(),
|
||||
keyboard_nav::Message::FocusNext => ret = widget::focus_next(),
|
||||
keyboard_nav::Message::FocusPrevious => ret = widget::focus_previous(),
|
||||
keyboard_nav::Message::Escape => {
|
||||
if self.search.is_active() {
|
||||
self.search.state = search::State::Inactive;
|
||||
self.search_clear();
|
||||
}
|
||||
}
|
||||
keyboard_nav::Message::Search => {
|
||||
return self.search.focus();
|
||||
}
|
||||
},
|
||||
Message::Page(page) => self.activate_page(page),
|
||||
Message::Drag => return drag(window::Id::new(0)),
|
||||
Message::Close => return close(window::Id::new(0)),
|
||||
Message::Minimize => return minimize(window::Id::new(0), true),
|
||||
Message::Maximize => return toggle_maximize(window::Id::new(0)),
|
||||
Message::NavBar(key) => {
|
||||
if let Some(page) = self.nav_bar.data::<page::Entity>(key).copied() {
|
||||
self.activate_page(page);
|
||||
}
|
||||
}
|
||||
Message::ToggleNavBar => self.nav_bar_toggled = !self.nav_bar_toggled,
|
||||
Message::ToggleNavBarCondensed => {
|
||||
self.nav_bar_toggled_condensed = !self.nav_bar_toggled_condensed;
|
||||
}
|
||||
Message::Search(search::Message::Activate) => {
|
||||
return self.search.focus();
|
||||
}
|
||||
Message::Search(search::Message::Changed(phrase)) => {
|
||||
self.search_changed(phrase);
|
||||
}
|
||||
Message::Search(search::Message::Clear) => {
|
||||
self.search_clear();
|
||||
}
|
||||
Message::Search(_) => {}
|
||||
Message::Desktop(message) => {
|
||||
if let Some(model) = self.pages.resource_mut::<desktop::Model>() {
|
||||
model.update(message);
|
||||
}
|
||||
}
|
||||
Message::DateAndTime(message) => {
|
||||
if let Some(model) = self.pages.resource_mut::<time::date::Model>() {
|
||||
model.update(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn view(&self) -> Element<Message> {
|
||||
let start = std::time::Instant::now();
|
||||
let (nav_bar_message, nav_bar_toggled) = if self.is_condensed {
|
||||
(
|
||||
Message::ToggleNavBarCondensed,
|
||||
self.nav_bar_toggled_condensed,
|
||||
)
|
||||
} else {
|
||||
(Message::ToggleNavBar, self.nav_bar_toggled)
|
||||
};
|
||||
|
||||
let mut header = header_bar()
|
||||
.title("")
|
||||
.on_close(Message::Close)
|
||||
.on_drag(Message::Drag)
|
||||
.start(
|
||||
iced::widget::row!(
|
||||
nav_bar_toggle()
|
||||
.on_nav_bar_toggled(nav_bar_message)
|
||||
.nav_bar_active(nav_bar_toggled),
|
||||
search::search(&self.search, Message::Search)
|
||||
)
|
||||
.align_items(iced::Alignment::Center)
|
||||
.into(),
|
||||
);
|
||||
|
||||
if self.show_maximize {
|
||||
header = header.on_maximize(Message::Maximize);
|
||||
}
|
||||
|
||||
if self.show_minimize {
|
||||
header = header.on_minimize(Message::Minimize);
|
||||
}
|
||||
|
||||
let header = Into::<Element<Message>>::into(header).debug(self.debug);
|
||||
|
||||
let mut widgets = Vec::with_capacity(2);
|
||||
|
||||
if nav_bar_toggled {
|
||||
let mut nav_bar = nav_bar(&self.nav_bar, Message::NavBar);
|
||||
|
||||
if !self.is_condensed {
|
||||
nav_bar = nav_bar.max_width(300);
|
||||
}
|
||||
|
||||
let nav_bar: Element<_> = nav_bar.into();
|
||||
widgets.push(nav_bar.debug(self.debug));
|
||||
}
|
||||
|
||||
if !(self.is_condensed && nav_bar_toggled) {
|
||||
widgets.push(
|
||||
scrollable(row![
|
||||
horizontal_space(Length::Fill),
|
||||
(if self.search.is_active() {
|
||||
self.search_view()
|
||||
} else if let Some(sub_pages) = self.pages.sub_pages(self.active_page) {
|
||||
self.sub_page_view(sub_pages)
|
||||
} else if let Some(content) = self.pages.content(self.active_page) {
|
||||
self.page_view(content)
|
||||
} else {
|
||||
panic!("page without sub-pages or content");
|
||||
})
|
||||
.debug(self.debug),
|
||||
horizontal_space(Length::Fill),
|
||||
])
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let content = container(row(widgets))
|
||||
.padding([0, 8, 8, 8])
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.into();
|
||||
|
||||
eprintln!(
|
||||
"view: {:?}",
|
||||
std::time::Instant::now().duration_since(start)
|
||||
);
|
||||
|
||||
column(vec![header, content]).into()
|
||||
}
|
||||
|
||||
fn theme(&self) -> Theme {
|
||||
self.theme
|
||||
}
|
||||
|
||||
fn scale_factor(&self) -> f64 {
|
||||
self.scaling_factor as f64
|
||||
}
|
||||
}
|
||||
|
||||
impl SettingsApp {
|
||||
/// Activates a page.
|
||||
fn activate_page(&mut self, page: page::Entity) {
|
||||
self.nav_bar_toggled_condensed = false;
|
||||
self.active_page = page;
|
||||
|
||||
self.search_clear();
|
||||
self.search.state = search::State::Inactive;
|
||||
self.activate_navbar(page);
|
||||
}
|
||||
|
||||
/// Activates the navbar item associated with a page.
|
||||
fn activate_navbar(&mut self, mut page: page::Entity) {
|
||||
if let Some(parent) = self.pages.pages[page].parent {
|
||||
page = parent;
|
||||
}
|
||||
|
||||
if let Some(nav_id) = self.pages.data(page) {
|
||||
self.nav_bar.activate(*nav_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a main page to the settings application.
|
||||
fn insert_page<P: Page>(&mut self) -> page::Insert {
|
||||
let id = self.pages.register::<P>().id();
|
||||
|
||||
self.navbar_insert(id);
|
||||
|
||||
if P::PERSISTENT_ID == self.config.nav_page {
|
||||
self.active_page = id;
|
||||
}
|
||||
|
||||
page::Insert {
|
||||
model: &mut self.pages,
|
||||
id,
|
||||
}
|
||||
}
|
||||
|
||||
fn navbar_insert(&mut self, id: page::Entity) -> segmented_button::SingleSelectEntityMut {
|
||||
let page = &self.pages.pages[id];
|
||||
|
||||
self.nav_bar
|
||||
.insert()
|
||||
.text(page.title.clone())
|
||||
.icon(IconSource::from(page.icon_name))
|
||||
.data(id)
|
||||
.with_id(|nav_id| self.pages.data_set(id, nav_id))
|
||||
}
|
||||
|
||||
/// Displays the view of a page.
|
||||
fn page_view(&self, content: &[section::Entity]) -> cosmic::Element<Message> {
|
||||
let page = &self.pages.pages[self.active_page];
|
||||
|
||||
let mut column_widgets = Vec::with_capacity(1);
|
||||
|
||||
if let Some(parent) = page.parent {
|
||||
column_widgets.push(parent_page_button(
|
||||
&self.pages.pages[parent],
|
||||
page,
|
||||
Message::Page(parent),
|
||||
));
|
||||
}
|
||||
|
||||
column_widgets.reserve_exact(1 + content.len());
|
||||
for id in content.iter().copied() {
|
||||
let section = &self.pages.sections[id];
|
||||
column_widgets.push((section.view_fn)(self, section));
|
||||
}
|
||||
|
||||
settings::view_column(column_widgets).into()
|
||||
}
|
||||
|
||||
fn search_changed(&mut self, phrase: String) {
|
||||
// If the text was cleared, clear the search results too.
|
||||
if phrase.is_empty() {
|
||||
self.search_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a case-insensitive regular expression for the search function.
|
||||
let search_expression = regex::RegexBuilder::new(&phrase)
|
||||
.case_insensitive(true)
|
||||
.unicode(true)
|
||||
.ignore_whitespace(true)
|
||||
.size_limit(16 * 1024)
|
||||
.build();
|
||||
|
||||
if let Ok(expression) = search_expression {
|
||||
// With the new search expression, generate new search results.
|
||||
let results: Vec<_> = self.pages.search(&expression).collect();
|
||||
|
||||
// Use the results if results were found.
|
||||
if !results.is_empty() {
|
||||
self.search_selections = results;
|
||||
}
|
||||
}
|
||||
|
||||
self.search.phrase = phrase;
|
||||
}
|
||||
|
||||
/// Clears the search results so that the search page will not be shown.
|
||||
fn search_clear(&mut self) {
|
||||
self.search_selections.clear();
|
||||
self.search.phrase.clear();
|
||||
}
|
||||
|
||||
/// Displays the search view.
|
||||
fn search_view(&self) -> cosmic::Element<Message> {
|
||||
let mut sections: Vec<cosmic::Element<Message>> = Vec::new();
|
||||
|
||||
let mut current_page = page::Entity::default();
|
||||
for (page, section) in self.search_selections.iter().copied() {
|
||||
let section = &self.pages.sections[section];
|
||||
|
||||
if page != current_page {
|
||||
current_page = page;
|
||||
sections.push(search_header(&self.pages, page));
|
||||
}
|
||||
|
||||
let section = (section.view_fn)(self, section)
|
||||
.apply(iced::widget::container)
|
||||
.padding([0, 0, 0, 48]);
|
||||
|
||||
sections.push(section.into());
|
||||
}
|
||||
|
||||
settings::view_column(sections).into()
|
||||
}
|
||||
|
||||
/// Displays the sub-pages view of a page.
|
||||
fn sub_page_view(&self, sub_pages: &[page::Entity]) -> cosmic::Element<Message> {
|
||||
let page = &self.pages.pages[self.active_page];
|
||||
|
||||
let mut column_widgets = Vec::with_capacity(sub_pages.len());
|
||||
column_widgets.push(page_title(page));
|
||||
|
||||
for entity in sub_pages.iter().copied() {
|
||||
let sub_page = &self.pages.pages[entity];
|
||||
column_widgets.push(sub_page_button(entity, sub_page));
|
||||
}
|
||||
|
||||
settings::view_column(column_widgets)
|
||||
.apply(Element::from)
|
||||
.map(Message::Page)
|
||||
}
|
||||
}
|
||||
40
src/localize.rs
Normal file
40
src/localize.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use i18n_embed::{
|
||||
fluent::{fluent_language_loader, FluentLanguageLoader},
|
||||
DefaultLocalizer, LanguageLoader, Localizer,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "i18n/"]
|
||||
struct Localizations;
|
||||
|
||||
pub static LANGUAGE_LOADER: Lazy<FluentLanguageLoader> = Lazy::new(|| {
|
||||
let loader: FluentLanguageLoader = fluent_language_loader!();
|
||||
|
||||
loader
|
||||
.load_fallback_language(&Localizations)
|
||||
.expect("Error while loading fallback language");
|
||||
|
||||
loader
|
||||
});
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! fl {
|
||||
($message_id:literal) => {{
|
||||
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id)
|
||||
}};
|
||||
|
||||
($message_id:literal, $($args:expr),*) => {{
|
||||
i18n_embed_fl::fl!($crate::localize::LANGUAGE_LOADER, $message_id, $($args), *)
|
||||
}};
|
||||
}
|
||||
|
||||
// Get the `Localizer` to be used for localizing this library.
|
||||
#[must_use]
|
||||
pub fn localizer() -> Box<dyn Localizer> {
|
||||
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
|
||||
}
|
||||
37
src/main.rs
Normal file
37
src/main.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![allow(clippy::cast_precision_loss)]
|
||||
#![allow(clippy::cast_sign_loss)]
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(clippy::cast_lossless)]
|
||||
|
||||
pub mod app;
|
||||
pub use app::{Message, SettingsApp};
|
||||
|
||||
#[macro_use]
|
||||
pub mod localize;
|
||||
|
||||
pub mod widget;
|
||||
|
||||
pub mod page;
|
||||
|
||||
use cosmic::{iced::Application, settings};
|
||||
use i18n_embed::DesktopLanguageRequester;
|
||||
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if iced fails to run the application.
|
||||
pub fn main() -> cosmic::iced::Result {
|
||||
let localizer = crate::localize::localizer();
|
||||
let requested_languages = DesktopLanguageRequester::requested_languages();
|
||||
|
||||
if let Err(error) = localizer.select(&requested_languages) {
|
||||
eprintln!("error while loading fluent localizations: {}", error);
|
||||
}
|
||||
|
||||
settings::set_default_icon_theme("Pop");
|
||||
let mut settings = settings();
|
||||
settings.window.min_size = Some((600, 300));
|
||||
SettingsApp::run(settings)
|
||||
}
|
||||
24
src/page/desktop/appearance.rs
Normal file
24
src/page/desktop/appearance.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "appearance";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("appearance"))
|
||||
.description(fl!("appearance", "desc"))
|
||||
.icon_name("preferences-pop-desktop-appearance-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(Section::new())])
|
||||
}
|
||||
}
|
||||
25
src/page/desktop/dock.rs
Normal file
25
src/page/desktop/dock.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "dock";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("dock"))
|
||||
.description(fl!("dock", "desc"))
|
||||
.icon_name("preferences-pop-desktop-dock-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(Section::new())])
|
||||
}
|
||||
}
|
||||
119
src/page/desktop/mod.rs
Normal file
119
src/page/desktop/mod.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod appearance;
|
||||
pub mod dock;
|
||||
pub mod notifications;
|
||||
pub mod options;
|
||||
pub mod wallpaper;
|
||||
pub mod workspaces;
|
||||
|
||||
use crate::page;
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "desktop";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("desktop"))
|
||||
.icon_name("video-display-symbolic")
|
||||
}
|
||||
|
||||
fn sub_pages(page: page::Insert) -> page::Insert {
|
||||
page.sub_page::<options::Page>()
|
||||
.sub_page::<wallpaper::Page>()
|
||||
.sub_page::<appearance::Page>()
|
||||
.sub_page::<dock::Page>()
|
||||
.sub_page::<workspaces::Page>()
|
||||
.sub_page::<notifications::Page>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Message {
|
||||
Slideshow(bool),
|
||||
SameBackground(bool),
|
||||
ShowWorkspacesButton(bool),
|
||||
ShowApplicationsButton(bool),
|
||||
ShowMinimizeButton(bool),
|
||||
ShowMaximizeButton(bool),
|
||||
TopLeftHotCorner(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
pub struct Model {
|
||||
pub top_left_hot_corner: bool,
|
||||
pub show_workspaces_button: bool,
|
||||
pub show_applications_button: bool,
|
||||
pub show_minimize_button: bool,
|
||||
pub show_maximize_button: bool,
|
||||
pub slideshow: bool,
|
||||
pub same_background: bool,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::SameBackground(value) => self.same_background = value,
|
||||
Message::ShowApplicationsButton(value) => self.show_applications_button = value,
|
||||
Message::ShowMaximizeButton(value) => self.show_maximize_button = value,
|
||||
Message::ShowMinimizeButton(value) => self.show_minimize_button = value,
|
||||
Message::ShowWorkspacesButton(value) => self.show_workspaces_button = value,
|
||||
Message::Slideshow(value) => self.slideshow = value,
|
||||
Message::TopLeftHotCorner(value) => self.top_left_hot_corner = value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl From<Page> for Message {
|
||||
// fn from(page: Page) -> Message {
|
||||
// Message::Page(page)
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub enum Output {
|
||||
// Page(Page),
|
||||
// }
|
||||
|
||||
// impl SubPage for DesktopPage {
|
||||
// //TODO: translate
|
||||
// fn title(&self) -> &'static str {
|
||||
// use DesktopPage::*;
|
||||
// match self {
|
||||
// Workspaces => "Workspaces",
|
||||
// Notifications => "Notifications",
|
||||
// }
|
||||
// }
|
||||
|
||||
// //TODO: translate
|
||||
// fn description(&self) -> &'static str {
|
||||
// use DesktopPage::*;
|
||||
// match self {
|
||||
// Workspaces => "Set workspace number, behavior, and placement.",
|
||||
// Notifications => {
|
||||
// "Do Not Disturb, lockscreen notifications, and per-application settings."
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn icon_name(&self) -> &'static str {
|
||||
// use DesktopPage::*;
|
||||
// match self {
|
||||
// Workspaces => "preferences-pop-desktop-workspaces-symbolic",
|
||||
// Notifications => "preferences-system-notifications-symbolic",
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn parent_page(&self) -> Page {
|
||||
// Page::Desktop(None)
|
||||
// }
|
||||
|
||||
// fn into_page(self) -> Page {
|
||||
// Page::Desktop(Some(self))
|
||||
// }
|
||||
// }
|
||||
25
src/page/desktop/notifications.rs
Normal file
25
src/page/desktop/notifications.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "notifications";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("notifications"))
|
||||
.description(fl!("notifications", "desc"))
|
||||
.icon_name("preferences-system-notifications-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(Section::new())])
|
||||
}
|
||||
}
|
||||
167
src/page/desktop/options.rs
Normal file
167
src/page/desktop/options.rs
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::Message;
|
||||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::widget::horizontal_space,
|
||||
iced::Length,
|
||||
widget::{settings, toggler},
|
||||
Element,
|
||||
};
|
||||
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "desktop-options";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("desktop-options"))
|
||||
.description(fl!("desktop-options", "desc"))
|
||||
.icon_name("video-display-symbolic")
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![
|
||||
sections.insert(super_key_action()),
|
||||
sections.insert(hot_corner()),
|
||||
sections.insert(top_panel()),
|
||||
sections.insert(window_controls()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hot_corner() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("hot-corner"))
|
||||
.descriptions(vec![fl!("hot-corner", "top-left-corner")])
|
||||
.view_fn(|app, section| {
|
||||
let desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
let descriptions = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(None, desktop.top_left_hot_corner, |value| {
|
||||
Message::TopLeftHotCorner(value)
|
||||
}),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::Message::Desktop)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn super_key_action() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("super-key-action"))
|
||||
.descriptions(vec![
|
||||
fl!("super-key-action", "launcher"),
|
||||
fl!("super-key-action", "workspaces"),
|
||||
fl!("super-key-action", "applications"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let _desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[2],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn top_panel() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("top-panel"))
|
||||
.descriptions(vec![
|
||||
fl!("top-panel", "workspaces"),
|
||||
fl!("top-panel", "applications"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_workspaces_button,
|
||||
Message::ShowWorkspacesButton,
|
||||
),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_applications_button,
|
||||
Message::ShowApplicationsButton,
|
||||
),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::Message::Desktop)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn window_controls() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("window-controls"))
|
||||
.descriptions(vec![
|
||||
fl!("window-controls", "minimize"),
|
||||
fl!("window-controls", "maximize"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_minimize_button,
|
||||
Message::ShowMinimizeButton,
|
||||
),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
toggler(
|
||||
None,
|
||||
desktop.show_maximize_button,
|
||||
Message::ShowMaximizeButton,
|
||||
),
|
||||
))
|
||||
.apply(Element::from)
|
||||
.map(crate::Message::Desktop)
|
||||
})
|
||||
}
|
||||
98
src/page/desktop/wallpaper.rs
Normal file
98
src/page/desktop/wallpaper.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::Message;
|
||||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::widget::{column, container, horizontal_space, image, row, svg, text},
|
||||
iced::Length,
|
||||
theme,
|
||||
widget::{list_column, settings, toggler},
|
||||
Element,
|
||||
};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "wallpaper";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("wallpaper"))
|
||||
.description(fl!("wallpaper", "desc"))
|
||||
.icon_name("preferences-desktop-wallpaper-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(settings())])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn settings() -> Section {
|
||||
Section::new()
|
||||
.descriptions(vec![
|
||||
fl!("wallpaper", "same"),
|
||||
fl!("wallpaper", "fit"),
|
||||
fl!("wallpaper", "slide"),
|
||||
fl!("wallpaper", "change"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
let desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
let image_paths: Vec<std::path::PathBuf> = Vec::new();
|
||||
|
||||
let mut image_column = Vec::with_capacity(image_paths.len() / 4);
|
||||
for chunk in image_paths.chunks(4) {
|
||||
let mut image_row = Vec::with_capacity(chunk.len());
|
||||
for image_path in chunk.iter() {
|
||||
image_row.push(if image_path.ends_with(".svg") {
|
||||
svg(svg::Handle::from_path(image_path))
|
||||
.width(Length::Units(150))
|
||||
.into()
|
||||
} else {
|
||||
image(image_path).width(Length::Units(150)).into()
|
||||
});
|
||||
}
|
||||
image_column.push(row(image_row).spacing(16).into());
|
||||
}
|
||||
|
||||
let children = vec![
|
||||
row!(
|
||||
horizontal_space(Length::Fill),
|
||||
container(
|
||||
image("/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png")
|
||||
.width(Length::Units(300))
|
||||
)
|
||||
.padding(4)
|
||||
.style(theme::Container::Box),
|
||||
horizontal_space(Length::Fill),
|
||||
)
|
||||
.into(),
|
||||
list_column()
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
toggler(None, desktop.same_background, Message::SameBackground),
|
||||
))
|
||||
.add(settings::item(&descriptions[1], text("TODO")))
|
||||
.add(settings::item(
|
||||
&descriptions[2],
|
||||
toggler(None, desktop.slideshow, Message::Slideshow),
|
||||
))
|
||||
.into(),
|
||||
column(image_column).spacing(16).into(),
|
||||
];
|
||||
|
||||
settings::view_column(children)
|
||||
.padding(0)
|
||||
.apply(Element::from)
|
||||
.map(crate::Message::Desktop)
|
||||
})
|
||||
}
|
||||
84
src/page/desktop/workspaces.rs
Normal file
84
src/page/desktop/workspaces.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cosmic::iced::{widget::horizontal_space, Length};
|
||||
use cosmic::widget::settings;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "workspaces";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("workspaces"))
|
||||
.description(fl!("workspaces", "desc"))
|
||||
.icon_name("preferences-pop-desktop-workspaces-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![
|
||||
sections.insert(behavior()),
|
||||
sections.insert(multi_behavior()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn behavior() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("workspaces-behavior"))
|
||||
.descriptions(vec![
|
||||
fl!("workspaces-behavior", "dynamic"),
|
||||
fl!("workspaces-behavior", "fixed"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let _desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn multi_behavior() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("workspaces-multi-behavior"))
|
||||
.descriptions(vec![
|
||||
fl!("workspaces-multi-behavior", "span"),
|
||||
fl!("workspaces-multi-behavior", "separate"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let _desktop = app
|
||||
.pages
|
||||
.resource::<super::Model>()
|
||||
.expect("desktop model is missing");
|
||||
let descriptions = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
57
src/page/mod.rs
Normal file
57
src/page/mod.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod desktop;
|
||||
pub mod networking;
|
||||
pub mod section;
|
||||
pub mod time;
|
||||
pub use section::Section;
|
||||
pub mod sound;
|
||||
pub mod system;
|
||||
|
||||
mod model;
|
||||
|
||||
pub use model::{Insert, Model};
|
||||
|
||||
use derive_setters::Setters;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// ID of a page
|
||||
pub struct Entity;
|
||||
}
|
||||
|
||||
pub trait Page {
|
||||
type Model: Default + 'static;
|
||||
|
||||
/// A unique identity that is the same between application runs.
|
||||
const PERSISTENT_ID: &'static str;
|
||||
|
||||
fn page() -> Meta;
|
||||
|
||||
#[must_use]
|
||||
fn content(_sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Attaches sub-pages to the page.
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
fn sub_pages(page: Insert) -> Insert {
|
||||
page
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Setters)]
|
||||
#[must_use]
|
||||
pub struct Meta {
|
||||
#[setters(into)]
|
||||
pub title: String,
|
||||
#[setters(into)]
|
||||
pub icon_name: &'static str,
|
||||
#[setters(into)]
|
||||
pub description: String,
|
||||
#[setters(strip_option)]
|
||||
pub parent: Option<Entity>,
|
||||
}
|
||||
|
||||
pub type Content = Vec<section::Entity>;
|
||||
211
src/page/model.rs
Normal file
211
src/page/model.rs
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
use crate::page::{self, section, Content, Meta, Page, Section};
|
||||
use regex::Regex;
|
||||
use slotmap::{SecondaryMap, SlotMap, SparseSecondaryMap};
|
||||
|
||||
pub struct Model {
|
||||
pub pages: SlotMap<page::Entity, Meta>,
|
||||
pub resource: HashMap<TypeId, Box<dyn Any>>,
|
||||
pub storage: HashMap<TypeId, SecondaryMap<page::Entity, Box<dyn Any>>>,
|
||||
pub sub_pages: SparseSecondaryMap<page::Entity, Vec<page::Entity>>,
|
||||
pub sections: SlotMap<section::Entity, Section>,
|
||||
pub content: SparseSecondaryMap<page::Entity, Content>,
|
||||
}
|
||||
|
||||
impl Default for Model {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: SparseSecondaryMap::new(),
|
||||
pages: SlotMap::with_key(),
|
||||
resource: HashMap::new(),
|
||||
sections: SlotMap::with_key(),
|
||||
storage: HashMap::new(),
|
||||
sub_pages: SparseSecondaryMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
/// Check if a page exists in the model.
|
||||
#[must_use]
|
||||
pub fn contains_item(&self, id: page::Entity) -> bool {
|
||||
self.pages.contains_key(id)
|
||||
}
|
||||
|
||||
/// Returns the content of a page, if it has any.
|
||||
#[must_use]
|
||||
pub fn content(&self, page: page::Entity) -> Option<&[section::Entity]> {
|
||||
self.content.get(page).map(Vec::as_slice)
|
||||
}
|
||||
|
||||
/// Get an immutable reference to data associated with a page.
|
||||
#[must_use]
|
||||
pub fn data<Data: 'static>(&self, id: page::Entity) -> Option<&Data> {
|
||||
self.storage
|
||||
.get(&TypeId::of::<Data>())
|
||||
.and_then(|storage| storage.get(id))
|
||||
.and_then(|data| data.downcast_ref())
|
||||
}
|
||||
|
||||
/// Get a mutable reference to data associated with a page.
|
||||
pub fn data_mut<Data: 'static>(&mut self, id: page::Entity) -> Option<&mut Data> {
|
||||
self.storage
|
||||
.get_mut(&TypeId::of::<Data>())
|
||||
.and_then(|storage| storage.get_mut(id))
|
||||
.and_then(|data| data.downcast_mut())
|
||||
}
|
||||
|
||||
/// Associates data with the item.
|
||||
pub fn data_set<Data: 'static>(&mut self, id: page::Entity, data: Data) {
|
||||
if self.contains_item(id) {
|
||||
self.storage
|
||||
.entry(TypeId::of::<Data>())
|
||||
.or_insert_with(SecondaryMap::new)
|
||||
.insert(id, Box::new(data));
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a specific data type from the item.
|
||||
pub fn data_remove<Data: 'static>(&mut self, id: page::Entity) {
|
||||
self.storage
|
||||
.get_mut(&TypeId::of::<Data>())
|
||||
.and_then(|storage| storage.remove(id));
|
||||
}
|
||||
|
||||
// Registers a new page in the settings panel.
|
||||
pub fn register<P: Page>(&mut self) -> Insert {
|
||||
let id = self.pages.insert(P::page());
|
||||
|
||||
if let Some(content) = P::content(&mut self.sections) {
|
||||
self.content.insert(id, content);
|
||||
}
|
||||
|
||||
self.resource_register::<P::Model>();
|
||||
|
||||
P::sub_pages(Insert { id, model: self })
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn resource<Resource: 'static>(&self) -> Option<&Resource> {
|
||||
self.resource
|
||||
.get(&TypeId::of::<Resource>())
|
||||
.and_then(|resource| resource.downcast_ref())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn resource_mut<Resource: 'static>(&mut self) -> Option<&mut Resource> {
|
||||
self.resource
|
||||
.get_mut(&TypeId::of::<Resource>())
|
||||
.and_then(|resource| resource.downcast_mut())
|
||||
}
|
||||
|
||||
#[allow(unused_must_use)]
|
||||
pub fn resource_register<Resource: Default + 'static>(&mut self) {
|
||||
self.resource
|
||||
.entry(TypeId::of::<Resource>())
|
||||
.or_insert_with(|| Box::new(Resource::default()));
|
||||
}
|
||||
|
||||
/// Finds content of panels that match the search.
|
||||
pub fn search<'a>(
|
||||
&'a self,
|
||||
rule: &'a Regex,
|
||||
) -> impl Iterator<Item = (page::Entity, section::Entity)> + 'a {
|
||||
SearchIter {
|
||||
content: self.content.iter(),
|
||||
model: self,
|
||||
sections: None,
|
||||
rule,
|
||||
page: page::Entity::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the sub-pages of a page, if it has any.
|
||||
pub fn sub_pages(&self, page: page::Entity) -> Option<&[page::Entity]> {
|
||||
self.sub_pages.get(page).map(AsRef::as_ref)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Insert<'a> {
|
||||
pub model: &'a mut Model,
|
||||
pub id: page::Entity,
|
||||
}
|
||||
|
||||
impl<'a> Insert<'a> {
|
||||
#[must_use]
|
||||
pub fn id(self) -> page::Entity {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn content(self, content: Content) -> Self {
|
||||
self.model.content.insert(self.id, content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a page and associates it with its parent page.
|
||||
#[allow(clippy::return_self_not_must_use)]
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn sub_page<P: Page>(self) -> Self {
|
||||
let page = self.model.pages.insert(Meta {
|
||||
parent: Some(self.id),
|
||||
..P::page()
|
||||
});
|
||||
|
||||
if let Some(content) = P::content(&mut self.model.sections) {
|
||||
self.model.content.insert(page, content);
|
||||
}
|
||||
|
||||
self.model.resource_register::<P::Model>();
|
||||
|
||||
self.model
|
||||
.sub_pages
|
||||
.entry(self.id)
|
||||
.expect("parent page missing")
|
||||
.and_modify(|v| v.push(page))
|
||||
.or_insert_with(|| vec![page]);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SearchIter<'a> {
|
||||
model: &'a Model,
|
||||
content: slotmap::sparse_secondary::Iter<'a, page::Entity, Content>,
|
||||
sections: Option<std::slice::Iter<'a, section::Entity>>,
|
||||
page: page::Entity,
|
||||
rule: &'a Regex,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SearchIter<'a> {
|
||||
type Item = (page::Entity, section::Entity);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
'outer: loop {
|
||||
if let Some(sections) = self.sections.as_mut() {
|
||||
for id in sections {
|
||||
if self.model.sections[*id].matches_search(self.rule) {
|
||||
return Some((self.page, *id));
|
||||
}
|
||||
}
|
||||
|
||||
self.sections = None;
|
||||
}
|
||||
|
||||
if let Some((page, content)) = self.content.next() {
|
||||
self.page = page;
|
||||
self.sections = Some(content.iter());
|
||||
continue 'outer;
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/page/networking/accounts.rs
Normal file
11
src/page/networking/accounts.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page;
|
||||
|
||||
pub fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("online-accounts"))
|
||||
.description(fl!("online-accounts", "desc"))
|
||||
.icon_name("goa-panel-symbolic")
|
||||
}
|
||||
5
src/page/networking/mod.rs
Normal file
5
src/page/networking/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod accounts;
|
||||
pub mod wired;
|
||||
11
src/page/networking/wired.rs
Normal file
11
src/page/networking/wired.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page;
|
||||
|
||||
pub fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("wired"))
|
||||
.description(fl!("wired", "desc"))
|
||||
.icon_name("network-workgroup-symbolic")
|
||||
}
|
||||
65
src/page/section.rs
Normal file
65
src/page/section.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use derive_setters::Setters;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::SettingsApp;
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// ID of a page section
|
||||
pub struct Entity;
|
||||
}
|
||||
|
||||
/// A searchable sub-component of a page. Searches can group multiple sections together.
|
||||
#[derive(Setters)]
|
||||
#[must_use]
|
||||
pub struct Section {
|
||||
#[setters(into)]
|
||||
pub title: String,
|
||||
pub descriptions: Vec<String>,
|
||||
pub view_fn: for<'a> fn(&'a SettingsApp, &'a Section) -> cosmic::Element<'a, crate::Message>,
|
||||
#[setters(bool)]
|
||||
pub search_ignore: bool,
|
||||
}
|
||||
|
||||
impl Section {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
title: String::new(),
|
||||
descriptions: Vec::new(),
|
||||
view_fn: Self::unimplemented,
|
||||
search_ignore: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn matches_search(&self, rule: &Regex) -> bool {
|
||||
if self.search_ignore {
|
||||
return false;
|
||||
}
|
||||
|
||||
if rule.is_match(self.title.as_str()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for description in &self.descriptions {
|
||||
if rule.is_match(description.as_str()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn unimplemented<'a>(
|
||||
_app: &'a SettingsApp,
|
||||
_section: &'a Section,
|
||||
) -> cosmic::Element<'a, crate::Message> {
|
||||
cosmic::widget::settings::view_column(vec![cosmic::widget::settings::view_section("")
|
||||
.add(crate::widget::unimplemented_page())
|
||||
.into()])
|
||||
.into()
|
||||
}
|
||||
}
|
||||
148
src/page/sound.rs
Normal file
148
src/page/sound.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page::{self, Content, Section};
|
||||
use cosmic::{iced, widget::settings};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use super::section;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Sound {}
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = Sound;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "sound";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("sound"))
|
||||
.description(fl!("sound", "desc"))
|
||||
.icon_name("multimedia-volume-control-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![
|
||||
sections.insert(output()),
|
||||
sections.insert(input()),
|
||||
sections.insert(alerts()),
|
||||
sections.insert(applications()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn alerts() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("sound-alerts"))
|
||||
.descriptions(vec![
|
||||
fl!("sound-alerts", "volume"),
|
||||
fl!("sound-alerts", "sound"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let _sound = app
|
||||
.pages
|
||||
.resource::<Sound>()
|
||||
.expect("sound model is missing");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn applications() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("sound-applications"))
|
||||
.descriptions(vec![fl!("sound-applications", "desc")])
|
||||
.view_fn(|app, section| {
|
||||
let _sound = app
|
||||
.pages
|
||||
.resource::<Sound>()
|
||||
.expect("sound model is missing");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn input() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("sound-input"))
|
||||
.descriptions(vec![
|
||||
fl!("sound-input", "volume"),
|
||||
fl!("sound-input", "device"),
|
||||
fl!("sound-input", "level"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let _sound = app
|
||||
.pages
|
||||
.resource::<Sound>()
|
||||
.expect("sound model is missing");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[2],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn output() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("sound-output"))
|
||||
.descriptions(vec![
|
||||
fl!("sound-output", "volume"),
|
||||
fl!("sound-output", "device"),
|
||||
fl!("sound-output", "level"),
|
||||
fl!("sound-output", "config"),
|
||||
fl!("sound-output", "balance"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let _sound = app
|
||||
.pages
|
||||
.resource::<Sound>()
|
||||
.expect("sound model is missing");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[0],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[2],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.add(settings::item(
|
||||
§ion.descriptions[3],
|
||||
iced::widget::text("TODO"),
|
||||
))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
111
src/page/system/about.rs
Normal file
111
src/page/system/about.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page::{self, Content, Section};
|
||||
use cosmic::{
|
||||
iced::{
|
||||
widget::{horizontal_space, row},
|
||||
Length,
|
||||
},
|
||||
widget::{icon, list_column, settings, text},
|
||||
};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "about";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("about"))
|
||||
.description(fl!("about", "desc"))
|
||||
.icon_name("help-about-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<page::section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![
|
||||
sections.insert(distributor_logo()),
|
||||
sections.insert(device()),
|
||||
sections.insert(hardware()),
|
||||
sections.insert(os()),
|
||||
sections.insert(related()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn device() -> Section {
|
||||
Section::new()
|
||||
.descriptions(vec![fl!("about-device")])
|
||||
.view_fn(|_app, section| {
|
||||
list_column()
|
||||
.add(settings::item(§ion.descriptions[0], text("TODO")))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn distributor_logo() -> Section {
|
||||
Section::new().search_ignore().view_fn(|_app, _section| {
|
||||
row!(
|
||||
horizontal_space(Length::Fill),
|
||||
icon("distributor-logo", 78),
|
||||
horizontal_space(Length::Fill),
|
||||
)
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn hardware() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("about-hardware"))
|
||||
.descriptions(vec![
|
||||
fl!("about-hardware", "model"),
|
||||
fl!("about-hardware", "memory"),
|
||||
fl!("about-hardware", "processor"),
|
||||
fl!("about-hardware", "graphics"),
|
||||
fl!("about-hardware", "disk-capacity"),
|
||||
])
|
||||
.view_fn(|_app, section| {
|
||||
let desc = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(&desc[0], text("TODO")))
|
||||
.add(settings::item(&desc[1], text("TODO")))
|
||||
.add(settings::item(&desc[2], text("TODO")))
|
||||
.add(settings::item(&desc[3], text("TODO")))
|
||||
.add(settings::item(&desc[4], text("TODO")))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn os() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("about-os"))
|
||||
.descriptions(vec![
|
||||
fl!("about-os", "os"),
|
||||
fl!("about-os", "os-architecture"),
|
||||
fl!("about-os", "desktop-environment"),
|
||||
fl!("about-os", "windowing-system"),
|
||||
])
|
||||
.view_fn(|_app, section| {
|
||||
let desc = §ion.descriptions;
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(&desc[0], text("TODO")))
|
||||
.add(settings::item(&desc[1], text("TODO")))
|
||||
.add(settings::item(&desc[2], text("TODO")))
|
||||
.add(settings::item(&desc[3], text("TODO")))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn related() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("about-related"))
|
||||
.descriptions(vec![fl!("about-related", "support")])
|
||||
.view_fn(|_app, section| {
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(§ion.descriptions[0], text("TODO")))
|
||||
.into()
|
||||
})
|
||||
}
|
||||
25
src/page/system/firmware.rs
Normal file
25
src/page/system/firmware.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "firmware";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("firmware"))
|
||||
.description(fl!("firmware", "desc"))
|
||||
.icon_name("firmware-manager-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(Section::new())])
|
||||
}
|
||||
}
|
||||
31
src/page/system/mod.rs
Normal file
31
src/page/system/mod.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub mod about;
|
||||
pub mod firmware;
|
||||
pub mod users;
|
||||
|
||||
use crate::page;
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "system";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("system"))
|
||||
.icon_name("system-users-symbolic")
|
||||
}
|
||||
|
||||
fn sub_pages(page: page::Insert) -> page::Insert {
|
||||
page.sub_page::<users::Page>()
|
||||
.sub_page::<about::Page>()
|
||||
.sub_page::<firmware::Page>()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Model {}
|
||||
25
src/page/system/users.rs
Normal file
25
src/page/system/users.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use slotmap::SlotMap;
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = super::Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "users";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("users"))
|
||||
.description(fl!("users", "desc"))
|
||||
.icon_name("system-users-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(Section::new())])
|
||||
}
|
||||
}
|
||||
141
src/page/time/date.rs
Normal file
141
src/page/time/date.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::{widget::horizontal_space, Length},
|
||||
widget::settings,
|
||||
};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Model {
|
||||
auto: bool,
|
||||
auto_timezone: bool,
|
||||
military_time: bool,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::Automatic(enable) => self.auto = enable,
|
||||
Message::AutomaticTimezone(enable) => self.auto_timezone = enable,
|
||||
Message::MilitaryTime(enable) => self.military_time = enable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Message {
|
||||
Automatic(bool),
|
||||
AutomaticTimezone(bool),
|
||||
MilitaryTime(bool),
|
||||
}
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = Model;
|
||||
|
||||
const PERSISTENT_ID: &'static str = "time-date";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("time-date"))
|
||||
.description(fl!("time-date", "desc"))
|
||||
.icon_name("preferences-system-time-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![
|
||||
sections.insert(date()),
|
||||
sections.insert(timezone()),
|
||||
sections.insert(format()),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
fn date() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("time-date"))
|
||||
.descriptions(vec![fl!("time-date", "auto"), fl!("time-date")])
|
||||
.view_fn(|app, section| {
|
||||
let model = app
|
||||
.pages
|
||||
.resource::<Model>()
|
||||
.expect("time & date model not found");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[0])
|
||||
.toggler(model.auto, Message::Automatic),
|
||||
)
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::Message::DateAndTime)
|
||||
})
|
||||
}
|
||||
|
||||
fn format() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("time-format"))
|
||||
.descriptions(vec![
|
||||
fl!("time-format", "twenty-four"),
|
||||
fl!("time-format", "first"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let model = app
|
||||
.pages
|
||||
.resource::<Model>()
|
||||
.expect("time & date model not found");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
// 24-hour toggle
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[0])
|
||||
.toggler(model.military_time, Message::MilitaryTime),
|
||||
)
|
||||
// First day of week
|
||||
.add(settings::item(
|
||||
§ion.descriptions[1],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::Message::DateAndTime)
|
||||
})
|
||||
}
|
||||
|
||||
fn timezone() -> Section {
|
||||
Section::new()
|
||||
.title(fl!("time-zone"))
|
||||
.descriptions(vec![
|
||||
fl!("time-zone", "auto"),
|
||||
fl!("time-zone", "auto-info"),
|
||||
fl!("time-zone"),
|
||||
])
|
||||
.view_fn(|app, section| {
|
||||
let model = app
|
||||
.pages
|
||||
.resource::<Model>()
|
||||
.expect("time & date model not found");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
// Automatic timezone toggle
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[0])
|
||||
.description(§ion.descriptions[1])
|
||||
.toggler(model.auto_timezone, Message::AutomaticTimezone),
|
||||
)
|
||||
// Time zone select
|
||||
.add(
|
||||
settings::item::builder(§ion.descriptions[2])
|
||||
.control(horizontal_space(Length::Fill)),
|
||||
)
|
||||
.apply(cosmic::Element::from)
|
||||
.map(crate::Message::DateAndTime)
|
||||
})
|
||||
}
|
||||
26
src/page/time/mod.rs
Normal file
26
src/page/time/mod.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page;
|
||||
|
||||
pub mod date;
|
||||
pub mod region;
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = ();
|
||||
|
||||
const PERSISTENT_ID: &'static str = "time";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("time"))
|
||||
.description(fl!("time", "desc"))
|
||||
.icon_name("preferences-system-time-symbolic")
|
||||
}
|
||||
|
||||
fn sub_pages(page: page::Insert) -> page::Insert {
|
||||
page.sub_page::<date::Page>().sub_page::<region::Page>()
|
||||
}
|
||||
}
|
||||
24
src/page/time/region.rs
Normal file
24
src/page/time/region.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::page::{self, section, Content, Section};
|
||||
use slotmap::SlotMap;
|
||||
|
||||
pub struct Page;
|
||||
|
||||
impl page::Page for Page {
|
||||
type Model = ();
|
||||
|
||||
const PERSISTENT_ID: &'static str = "time-region";
|
||||
|
||||
fn page() -> page::Meta {
|
||||
page::Meta::default()
|
||||
.title(fl!("time-region"))
|
||||
.description(fl!("time-region", "desc"))
|
||||
.icon_name("preferences-desktop-locale-symbolic")
|
||||
}
|
||||
|
||||
fn content(sections: &mut SlotMap<section::Entity, Section>) -> Option<Content> {
|
||||
Some(vec![sections.insert(Section::new())])
|
||||
}
|
||||
}
|
||||
111
src/widget/mod.rs
Normal file
111
src/widget/mod.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use apply::Apply;
|
||||
use cosmic::iced::{
|
||||
self,
|
||||
widget::{button, column, container, horizontal_space, row, vertical_space, Button},
|
||||
Length,
|
||||
};
|
||||
use cosmic::widget::{divider, icon, list, settings, text};
|
||||
use cosmic::{theme, Element};
|
||||
|
||||
use crate::page::{self, Meta};
|
||||
|
||||
#[must_use]
|
||||
pub fn search_header(pages: &page::Model, page: page::Entity) -> cosmic::Element<crate::Message> {
|
||||
let page_meta = &pages.pages[page];
|
||||
|
||||
let mut column_children = Vec::with_capacity(4);
|
||||
|
||||
if let Some(parent) = page_meta.parent {
|
||||
let parent_meta = &pages.pages[parent];
|
||||
|
||||
column_children.push(
|
||||
text(parent_meta.title.as_str())
|
||||
.size(14)
|
||||
.apply(container)
|
||||
.padding([0, 0, 0, 6])
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
column_children.push(
|
||||
crate::widget::search_page_link(&page_meta.title)
|
||||
.on_press(crate::Message::Page(page))
|
||||
.into(),
|
||||
);
|
||||
|
||||
column_children.push(vertical_space(Length::Units(8)).into());
|
||||
column_children.push(divider::horizontal::heavy().into());
|
||||
|
||||
column(column_children).into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn search_page_link<Message: 'static>(title: &str) -> Button<Message, cosmic::Renderer> {
|
||||
text(title)
|
||||
.size(30)
|
||||
.horizontal_alignment(iced::alignment::Horizontal::Left)
|
||||
.apply(button)
|
||||
.style(cosmic::theme::Button::Link)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn page_title<Message: 'static>(page: &Meta) -> Element<Message> {
|
||||
row!(
|
||||
text(page.title.as_str()).size(30),
|
||||
horizontal_space(Length::Fill)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn parent_page_button<'a, Message: Clone + 'static>(
|
||||
parent: &'a Meta,
|
||||
sub_page: &'a Meta,
|
||||
on_press: Message,
|
||||
) -> Element<'a, Message> {
|
||||
column!(
|
||||
button(row!(
|
||||
icon("go-previous-symbolic", 16).style(theme::Svg::SymbolicLink),
|
||||
text(parent.title.as_str()).size(16),
|
||||
))
|
||||
.padding(0)
|
||||
.style(theme::Button::Link)
|
||||
.on_press(on_press),
|
||||
row!(
|
||||
text(sub_page.title.as_str()).size(30),
|
||||
horizontal_space(Length::Fill),
|
||||
),
|
||||
)
|
||||
.spacing(10)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn sub_page_button(entity: page::Entity, page: &Meta) -> Element<page::Entity> {
|
||||
settings::item::builder(page.title.as_str())
|
||||
.description(page.description.as_str())
|
||||
.icon(icon(page.icon_name, 20).style(theme::Svg::Symbolic))
|
||||
.control(row!(
|
||||
horizontal_space(Length::Fill),
|
||||
icon("go-next-symbolic", 20).style(theme::Svg::Symbolic)
|
||||
))
|
||||
.spacing(16)
|
||||
.apply(container)
|
||||
.padding([20, 24])
|
||||
.style(theme::Container::Custom(list::column::style))
|
||||
.apply(button)
|
||||
.padding(0)
|
||||
.style(theme::Button::Transparent)
|
||||
.on_press(entity)
|
||||
.into()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn unimplemented_page<Message: 'static>() -> Element<'static, Message> {
|
||||
settings::view_section("")
|
||||
.add(text("We haven't created that panel yet, and/or it is using a similar idea as current Pop! designs."))
|
||||
.into()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue