Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
789d54e2ea | ||
|
|
be726ddde8 | ||
|
|
96ff974b82 | ||
|
|
226bca632e | ||
|
|
446720c578 | ||
|
|
7625635050 | ||
|
|
8576ac5c44 | ||
|
|
8518262053 | ||
|
|
a29df37e77 | ||
|
|
2d755d37e5 | ||
|
|
001dd5473a | ||
|
|
120580994c | ||
|
|
18e088d593 | ||
|
|
2c6338f82f | ||
|
|
882fa30b66 | ||
|
|
3c564a7774 | ||
|
|
73f0e7e48e | ||
|
|
5e72a7ba32 | ||
|
|
de80b99e64 |
37
.github/workflows/build.yml
vendored
Normal file
37
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: sudo apt install libgtk-3-dev libgtk-layer-shell-dev
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --verbose
|
||||||
|
|
||||||
|
- name: Clippy
|
||||||
|
uses: actions-rs/clippy-check@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
args: --all-features
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: cargo fmt --check
|
||||||
45
.github/workflows/deploy.yml
vendored
Normal file
45
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]+.[0-9]+.[0-9]+
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Update CHANGELOG
|
||||||
|
id: changelog
|
||||||
|
uses: Requarks/changelog-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ github.token }}
|
||||||
|
tag: ${{ github.ref_name }}
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
allowUpdates: true
|
||||||
|
draft: false
|
||||||
|
name: ${{ github.ref_name }}
|
||||||
|
body: ${{ steps.changelog.outputs.changes }}
|
||||||
|
token: ${{ github.token }}
|
||||||
|
|
||||||
|
- name: Commit CHANGELOG.md
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v4
|
||||||
|
with:
|
||||||
|
branch: main
|
||||||
|
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
|
||||||
|
file_pattern: CHANGELOG.md
|
||||||
|
|
||||||
|
- uses: katyo/publish-crates@v1
|
||||||
|
with:
|
||||||
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
1
.idea/ironbar.iml
generated
1
.idea/ironbar.iml
generated
@@ -2,6 +2,7 @@
|
|||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
|
|||||||
1
.idea/runConfigurations/Run.xml
generated
1
.idea/runConfigurations/Run.xml
generated
@@ -10,6 +10,7 @@
|
|||||||
<option name="buildTarget" value="REMOTE" />
|
<option name="buildTarget" value="REMOTE" />
|
||||||
<option name="backtrace" value="SHORT" />
|
<option name="backtrace" value="SHORT" />
|
||||||
<envs>
|
<envs>
|
||||||
|
<env name="IRONBAR_CONFIG" value="examples/config.json" />
|
||||||
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="isRedirectInput" value="false" />
|
<option name="isRedirectInput" value="false" />
|
||||||
|
|||||||
1
.idea/runConfigurations/Run__Debug_.xml
generated
1
.idea/runConfigurations/Run__Debug_.xml
generated
@@ -11,6 +11,7 @@
|
|||||||
<option name="backtrace" value="SHORT" />
|
<option name="backtrace" value="SHORT" />
|
||||||
<envs>
|
<envs>
|
||||||
<env name="GTK_DEBUG" value="interactive" />
|
<env name="GTK_DEBUG" value="interactive" />
|
||||||
|
<env name="IRONBAR_CONFIG" value="examples/config.json" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="isRedirectInput" value="false" />
|
<option name="isRedirectInput" value="false" />
|
||||||
<option name="redirectInputPath" value="" />
|
<option name="redirectInputPath" value="" />
|
||||||
|
|||||||
19
.idea/runConfigurations/Run__Live_Config_.xml
generated
Normal file
19
.idea/runConfigurations/Run__Live_Config_.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Run (Live Config)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="command" value="run" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<option name="channel" value="DEFAULT" />
|
||||||
|
<option name="requiredFeatures" value="true" />
|
||||||
|
<option name="allFeatures" value="false" />
|
||||||
|
<option name="emulateTerminal" value="false" />
|
||||||
|
<option name="withSudo" value="false" />
|
||||||
|
<option name="buildTarget" value="REMOTE" />
|
||||||
|
<option name="backtrace" value="SHORT" />
|
||||||
|
<envs />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="CARGO.BUILD_TASK_PROVIDER" enabled="true" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1033,7 +1033,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"cornfig",
|
"cornfig",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Customisable wlroots/sway bar"
|
description = "Customisable wlroots/sway bar"
|
||||||
|
|||||||
77
README.md
77
README.md
@@ -8,82 +8,39 @@ For information and examples on styling please see the [wiki](https://github.com
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install with cargo:
|
Run using `ironbar`.
|
||||||
|
|
||||||
|
### Cargo
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo install ironbar
|
cargo install ironbar
|
||||||
```
|
```
|
||||||
|
|
||||||
Then just run with `ironbar`.
|
[crate](https://crates.io/crates/ironbar)
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
```sh
|
||||||
|
yay -S ironbar-git
|
||||||
|
```
|
||||||
|
|
||||||
|
[aur package](https://aur.archlinux.org/packages/ironbar-git)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
By default, running will get you a blank bar. To start, you will need a configuration file in `.config/ironbar`. This could be called `config.<fmt>`, using one of the available extensions:
|
Ironbar gives a lot of flexibility when configuring, including multiple file formats
|
||||||
|
and options for scaling complexity: you can use a single config across all monitors,
|
||||||
|
or configure different/multiple bars per monitor.
|
||||||
|
|
||||||
- JSON
|
A full configuration guide can be found [here](https://github.com/JakeStanger/ironbar/wiki/configuration-guide).
|
||||||
- TOML
|
|
||||||
- YAML
|
|
||||||
- [Corn](https://github.com/jakestanger/corn) (Experimental. JSON/Nix like config lang. Supports variables.)
|
|
||||||
|
|
||||||
For a full list of modules and their configuration options, please see the [wiki](https://github.com/JakeStanger/ironbar/wiki).
|
|
||||||
|
|
||||||
There are two different approaches to configuring the bar:
|
|
||||||
|
|
||||||
### Same configuration across all monitors
|
|
||||||
|
|
||||||
> If you have a single monitor, or want the same bar to appear across each of your monitors, choose this option.
|
|
||||||
|
|
||||||
The top-level object takes any combination of `left`, `center`, and `right`. These each take a list of modules and determine where they are positioned.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"left": [],
|
|
||||||
"center": [],
|
|
||||||
"right": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Different configuration across monitors
|
|
||||||
|
|
||||||
> If you have multiple monitors and want them to differ in configuration, choose this option.
|
|
||||||
|
|
||||||
The top-level object takes a single key called `monitors`. This takes an array where each entry is an object with a configuration for each monitor.
|
|
||||||
The monitor's config object takes any combination of `left`, `center`, and `right`. These each take a list of modules and determine where they are positioned.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"monitors": [
|
|
||||||
{
|
|
||||||
"left": [],
|
|
||||||
"center": [],
|
|
||||||
"right": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"left": [],
|
|
||||||
"center": [],
|
|
||||||
"right": []
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
|------------|-------------------|---------|-----------------------------------------------------------------------------|
|
|
||||||
| `position` | `top` or `bottom` | `[]` | The bar's position on screen. |
|
|
||||||
| `height` | `integer` | `42` | The bar's height in pixels. |
|
|
||||||
| `left` | `Module[]` | `[]` | Array of left modules. |
|
|
||||||
| `center` | `Module[]` | `[]` | Array of center modules. |
|
|
||||||
| `right` | `Module[]` | `[]` | Array of right modules. |
|
|
||||||
| `monitors` | `RootConfig[]` | `null` | Array of root config objects for each monitor. Overrides left/center/right. |
|
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|
||||||
To get started, create a stylesheet at `.config/ironbar/style.css`. Changes will be hot-reloaded every time you save the file.
|
To get started, create a stylesheet at `.config/ironbar/style.css`. Changes will be hot-reloaded every time you save the file.
|
||||||
|
|
||||||
An example stylesheet and information about each module's styling information can be found on the [wiki](https://github.com/JakeStanger/ironbar/wiki).
|
A full styling guide can be found [here](https://github.com/JakeStanger/ironbar/wiki/styling-guide).
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
|
|||||||
43
examples/config.corn
Normal file
43
examples/config.corn
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
let {
|
||||||
|
$workspaces = {
|
||||||
|
type = "workspaces"
|
||||||
|
all_monitors = false
|
||||||
|
name_map = {
|
||||||
|
1 = "ﭮ"
|
||||||
|
2 = ""
|
||||||
|
3 = ""
|
||||||
|
Games = ""
|
||||||
|
Code = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$launcher = {
|
||||||
|
type = "launcher"
|
||||||
|
favorites = ["firefox" "discord" "Steam"]
|
||||||
|
show_names = false
|
||||||
|
show_icons = true
|
||||||
|
icon_theme = "Paper"
|
||||||
|
}
|
||||||
|
|
||||||
|
$mpd_local = { type = "mpd" music_dir = "/home/jake/Music" }
|
||||||
|
$mpd_server = { type = "mpd" host = "chloe:6600" }
|
||||||
|
|
||||||
|
$sys_info = {
|
||||||
|
type = "sys-info"
|
||||||
|
format = ["{cpu-percent}% " "{memory-percent}% "]
|
||||||
|
}
|
||||||
|
|
||||||
|
$tray = { type = "tray" }
|
||||||
|
$clock = { type = "clock" }
|
||||||
|
|
||||||
|
$phone_battery = {
|
||||||
|
type = "script"
|
||||||
|
path = "/home/jake/bin/phone-battery"
|
||||||
|
}
|
||||||
|
|
||||||
|
$left = [ $workspaces $launcher ]
|
||||||
|
$right = [ $mpd_local $mpd_server $phone_battery $sys_info $clock ]
|
||||||
|
}
|
||||||
|
in {
|
||||||
|
left = $left right = $right
|
||||||
|
}
|
||||||
18
examples/config.json
Normal file
18
examples/config.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"monitors": {
|
||||||
|
"DP-1": [
|
||||||
|
{
|
||||||
|
"left": [{"type": "clock"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"position": "top",
|
||||||
|
"left": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"DP-2": {
|
||||||
|
"position": "bottom",
|
||||||
|
"height": 30,
|
||||||
|
"left": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
148
examples/style.css
Normal file
148
examples/style.css
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
* {
|
||||||
|
/* `otf-font-awesome` is required to be installed for icons */
|
||||||
|
font-family: Noto Sans Nerd Font, sans-serif;
|
||||||
|
/* font-family: 'Jetbrains Mono', monospace;*/
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
/*color: white;*/
|
||||||
|
/*background-color: #2d2d2d;*/
|
||||||
|
/*background-color: red;*/
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
/*opacity: 0.4;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#bar {
|
||||||
|
border-top: 1px solid #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* test 34543*/
|
||||||
|
|
||||||
|
#right > * + * {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces .item {
|
||||||
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces .item.focused {
|
||||||
|
box-shadow: inset 0 -3px;
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#workspaces *:not(.focused):hover {
|
||||||
|
box-shadow: inset 0 -3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .item {
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .item:not(.focused):hover {
|
||||||
|
background-color: #1c1c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .open {
|
||||||
|
border-bottom: 2px solid #6699cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .focused {
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
border-bottom: 4px solid #6699cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#launcher .urgent {
|
||||||
|
color: white;
|
||||||
|
background-color: #8f0a0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#clock {
|
||||||
|
color: white;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#script {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sysinfo {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tray .item {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mpd {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
border: 1px solid #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-clock {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar-clock {
|
||||||
|
color: white;
|
||||||
|
font-size: 2.5em;
|
||||||
|
padding-bottom: 0.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar .header {
|
||||||
|
padding-top: 1em;
|
||||||
|
border-top: 1px solid #424242;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar:selected {
|
||||||
|
background-color: #6699cc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-mpd {
|
||||||
|
color: white;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-mpd #album-art {
|
||||||
|
/*border: 1px solid #424242;*/
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-mpd #title .icon, #popup-mpd #title .label {
|
||||||
|
font-size: 1.7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-mpd #controls * {
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#popup-mpd #controls *:disabled {
|
||||||
|
color: #424242;
|
||||||
|
}
|
||||||
|
|
||||||
|
#focused {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
70
src/bar.rs
70
src/bar.rs
@@ -31,7 +31,7 @@ pub fn create_bar(app: &Application, monitor: &Monitor, monitor_name: &str, conf
|
|||||||
content.set_center_widget(Some(¢er));
|
content.set_center_widget(Some(¢er));
|
||||||
content.pack_end(&right, false, false, 0);
|
content.pack_end(&right, false, false, 0);
|
||||||
|
|
||||||
load_modules(&left, ¢er, &right, app, config, monitor_name);
|
load_modules(&left, ¢er, &right, app, config, monitor, monitor_name);
|
||||||
win.add(&content);
|
win.add(&content);
|
||||||
|
|
||||||
win.connect_destroy_event(|_, _| {
|
win.connect_destroy_event(|_, _| {
|
||||||
@@ -48,6 +48,7 @@ fn load_modules(
|
|||||||
right: >k::Box,
|
right: >k::Box,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
monitor: &Monitor,
|
||||||
output_name: &str,
|
output_name: &str,
|
||||||
) {
|
) {
|
||||||
if let Some(modules) = config.left {
|
if let Some(modules) = config.left {
|
||||||
@@ -55,10 +56,11 @@ fn load_modules(
|
|||||||
app,
|
app,
|
||||||
location: ModuleLocation::Left,
|
location: ModuleLocation::Left,
|
||||||
bar_position: &config.position,
|
bar_position: &config.position,
|
||||||
|
monitor,
|
||||||
output_name,
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(left, modules, info);
|
add_modules(left, modules, &info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(modules) = config.center {
|
if let Some(modules) = config.center {
|
||||||
@@ -66,10 +68,11 @@ fn load_modules(
|
|||||||
app,
|
app,
|
||||||
location: ModuleLocation::Center,
|
location: ModuleLocation::Center,
|
||||||
bar_position: &config.position,
|
bar_position: &config.position,
|
||||||
|
monitor,
|
||||||
output_name,
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(center, modules, info);
|
add_modules(center, modules, &info);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(modules) = config.right {
|
if let Some(modules) = config.right {
|
||||||
@@ -77,56 +80,33 @@ fn load_modules(
|
|||||||
app,
|
app,
|
||||||
location: ModuleLocation::Right,
|
location: ModuleLocation::Right,
|
||||||
bar_position: &config.position,
|
bar_position: &config.position,
|
||||||
|
monitor,
|
||||||
output_name,
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(right, modules, info);
|
add_modules(right, modules, &info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: ModuleInfo) {
|
fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: &ModuleInfo) {
|
||||||
|
macro_rules! add_module {
|
||||||
|
($module:expr, $name:literal) => {{
|
||||||
|
let widget = $module.into_widget(&info);
|
||||||
|
widget.set_widget_name($name);
|
||||||
|
content.add(&widget);
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
for config in modules {
|
for config in modules {
|
||||||
match config {
|
match config {
|
||||||
ModuleConfig::Clock(module) => {
|
ModuleConfig::Clock(module) => add_module!(module, "clock"),
|
||||||
let widget = module.into_widget(&info);
|
ModuleConfig::Mpd(module) => add_module!(module, "mpd"),
|
||||||
widget.set_widget_name("clock");
|
ModuleConfig::Tray(module) => add_module!(module, "tray"),
|
||||||
content.add(&widget);
|
ModuleConfig::Workspaces(module) => add_module!(module, "workspaces"),
|
||||||
}
|
ModuleConfig::SysInfo(module) => add_module!(module, "sysinfo"),
|
||||||
ModuleConfig::Mpd(module) => {
|
ModuleConfig::Launcher(module) => add_module!(module, "launcher"),
|
||||||
let widget = module.into_widget(&info);
|
ModuleConfig::Script(module) => add_module!(module, "script"),
|
||||||
widget.set_widget_name("mpd");
|
ModuleConfig::Focused(module) => add_module!(module, "focused"),
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
ModuleConfig::Tray(module) => {
|
|
||||||
let widget = module.into_widget(&info);
|
|
||||||
widget.set_widget_name("tray");
|
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
ModuleConfig::Workspaces(module) => {
|
|
||||||
let widget = module.into_widget(&info);
|
|
||||||
widget.set_widget_name("workspaces");
|
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
ModuleConfig::SysInfo(module) => {
|
|
||||||
let widget = module.into_widget(&info);
|
|
||||||
widget.set_widget_name("sysinfo");
|
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
ModuleConfig::Launcher(module) => {
|
|
||||||
let widget = module.into_widget(&info);
|
|
||||||
widget.set_widget_name("launcher");
|
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
ModuleConfig::Script(module) => {
|
|
||||||
let widget = module.into_widget(&info);
|
|
||||||
widget.set_widget_name("script");
|
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
ModuleConfig::Focused(module) => {
|
|
||||||
let widget = module.into_widget(&info);
|
|
||||||
widget.set_widget_name("focused");
|
|
||||||
content.add(&widget);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ use crate::modules::tray::TrayModule;
|
|||||||
use crate::modules::workspaces::WorkspacesModule;
|
use crate::modules::workspaces::WorkspacesModule;
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::fs;
|
use std::collections::HashMap;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{env, fs};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||||
@@ -23,6 +25,13 @@ pub enum ModuleConfig {
|
|||||||
Focused(FocusedModule),
|
Focused(FocusedModule),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum MonitorConfig {
|
||||||
|
Single(Config),
|
||||||
|
Multiple(Vec<Config>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum BarPosition {
|
pub enum BarPosition {
|
||||||
@@ -32,7 +41,7 @@ pub enum BarPosition {
|
|||||||
|
|
||||||
impl Default for BarPosition {
|
impl Default for BarPosition {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
BarPosition::Bottom
|
Self::Bottom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +56,7 @@ pub struct Config {
|
|||||||
pub center: Option<Vec<ModuleConfig>>,
|
pub center: Option<Vec<ModuleConfig>>,
|
||||||
pub right: Option<Vec<ModuleConfig>>,
|
pub right: Option<Vec<ModuleConfig>>,
|
||||||
|
|
||||||
pub monitors: Option<Vec<Config>>,
|
pub monitors: Option<HashMap<String, MonitorConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn default_bar_position() -> BarPosition {
|
const fn default_bar_position() -> BarPosition {
|
||||||
@@ -60,35 +69,49 @@ const fn default_bar_height() -> i32 {
|
|||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load() -> Option<Self> {
|
pub fn load() -> Option<Self> {
|
||||||
let config_dir = config_dir().expect("Failed to locate user config dir");
|
if let Ok(config_path) = env::var("IRONBAR_CONFIG") {
|
||||||
|
let path = PathBuf::from(config_path);
|
||||||
|
Self::load_file(
|
||||||
|
&path,
|
||||||
|
path.extension()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let config_dir = config_dir().expect("Failed to locate user config dir");
|
||||||
|
|
||||||
let extensions = vec!["json", "toml", "yaml", "yml", "corn"];
|
let extensions = vec!["json", "toml", "yaml", "yml", "corn"];
|
||||||
|
|
||||||
extensions.into_iter().find_map(|extension| {
|
extensions.into_iter().find_map(|extension| {
|
||||||
let full_path = config_dir
|
let full_path = config_dir
|
||||||
.join("ironbar")
|
.join("ironbar")
|
||||||
.join(format!("config.{extension}"));
|
.join(format!("config.{extension}"));
|
||||||
|
|
||||||
if full_path.exists() {
|
Self::load_file(&full_path, extension)
|
||||||
let file = fs::read(full_path).expect("Failed to read config file");
|
})
|
||||||
Some(match extension {
|
}
|
||||||
"json" => serde_json::from_slice(&file).expect("Invalid JSON config"),
|
}
|
||||||
"toml" => toml::from_slice(&file).expect("Invalid TOML config"),
|
|
||||||
"yaml" | "yml" => serde_yaml::from_slice(&file).expect("Invalid YAML config"),
|
fn load_file(path: &Path, extension: &str) -> Option<Self> {
|
||||||
"corn" => {
|
if path.exists() {
|
||||||
// corn doesn't support deserialization yet
|
let file = fs::read(path).expect("Failed to read config file");
|
||||||
// so serialize the interpreted result then deserialize that
|
Some(match extension {
|
||||||
let file =
|
"json" => serde_json::from_slice(&file).expect("Invalid JSON config"),
|
||||||
String::from_utf8(file).expect("Config file contains invalid UTF-8");
|
"toml" => toml::from_slice(&file).expect("Invalid TOML config"),
|
||||||
let config = cornfig::parse(&file).expect("Invalid corn config").value;
|
"yaml" | "yml" => serde_yaml::from_slice(&file).expect("Invalid YAML config"),
|
||||||
serde_json::from_str(&serde_json::to_string(&config).unwrap()).unwrap()
|
"corn" => {
|
||||||
}
|
// corn doesn't support deserialization yet
|
||||||
_ => unreachable!(),
|
// so serialize the interpreted result then deserialize that
|
||||||
})
|
let file = String::from_utf8(file).expect("Config file contains invalid UTF-8");
|
||||||
} else {
|
let config = cornfig::parse(&file).expect("Invalid corn config").value;
|
||||||
None
|
serde_json::from_str(&serde_json::to_string(&config).unwrap()).unwrap()
|
||||||
}
|
}
|
||||||
})
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
27
src/main.rs
27
src/main.rs
@@ -8,7 +8,7 @@ mod style;
|
|||||||
mod sway;
|
mod sway;
|
||||||
|
|
||||||
use crate::bar::create_bar;
|
use crate::bar::create_bar;
|
||||||
use crate::config::Config;
|
use crate::config::{Config, MonitorConfig};
|
||||||
use crate::style::load_css;
|
use crate::style::load_css;
|
||||||
use crate::sway::SwayOutput;
|
use crate::sway::SwayOutput;
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
@@ -40,6 +40,7 @@ async fn main() {
|
|||||||
|
|
||||||
let display = gdk::Display::default().expect("Failed to get default GDK display");
|
let display = gdk::Display::default().expect("Failed to get default GDK display");
|
||||||
let num_monitors = display.n_monitors();
|
let num_monitors = display.n_monitors();
|
||||||
|
|
||||||
for i in 0..num_monitors {
|
for i in 0..num_monitors {
|
||||||
let monitor = display.monitor(i).unwrap();
|
let monitor = display.monitor(i).unwrap();
|
||||||
let monitor_name = &outputs
|
let monitor_name = &outputs
|
||||||
@@ -47,11 +48,25 @@ async fn main() {
|
|||||||
.expect("GTK monitor output differs from Sway's")
|
.expect("GTK monitor output differs from Sway's")
|
||||||
.name;
|
.name;
|
||||||
|
|
||||||
let config = config.monitors.as_ref().map_or(&config, |monitor_config| {
|
config.monitors.as_ref().map_or_else(
|
||||||
monitor_config.get(i as usize).unwrap_or(&config)
|
|| {
|
||||||
});
|
create_bar(app, &monitor, monitor_name, config.clone());
|
||||||
|
},
|
||||||
create_bar(app, &monitor, monitor_name, config.clone());
|
|config| {
|
||||||
|
let config = config.get(monitor_name);
|
||||||
|
match &config {
|
||||||
|
Some(MonitorConfig::Single(config)) => {
|
||||||
|
create_bar(app, &monitor, monitor_name, config.clone());
|
||||||
|
}
|
||||||
|
Some(MonitorConfig::Multiple(configs)) => {
|
||||||
|
for config in configs {
|
||||||
|
create_bar(app, &monitor, monitor_name, config.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let style_path = config_dir()
|
let style_path = config_dir()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ mod popup;
|
|||||||
|
|
||||||
use self::popup::Popup;
|
use self::popup::Popup;
|
||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use crate::popup::PopupAlignment;
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
@@ -33,6 +32,7 @@ impl Module<Button> for ClockModule {
|
|||||||
let popup = Popup::new(
|
let popup = Popup::new(
|
||||||
"popup-clock",
|
"popup-clock",
|
||||||
info.app,
|
info.app,
|
||||||
|
info.monitor,
|
||||||
Orientation::Vertical,
|
Orientation::Vertical,
|
||||||
info.bar_position,
|
info.bar_position,
|
||||||
);
|
);
|
||||||
@@ -41,14 +41,7 @@ impl Module<Button> for ClockModule {
|
|||||||
button.show_all();
|
button.show_all();
|
||||||
|
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |button| {
|
||||||
let button_w = button.allocation().width();
|
popup.show(button);
|
||||||
|
|
||||||
let (button_x, _) = button
|
|
||||||
.translate_coordinates(&button.toplevel().unwrap(), 0, 0)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
popup.show();
|
|
||||||
popup.set_pos(f64::from(button_x + button_w), PopupAlignment::Right);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|||||||
@@ -73,10 +73,7 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
|
|
||||||
{
|
{
|
||||||
rx.attach(None, move |node| {
|
rx.attach(None, move |node| {
|
||||||
let value = node
|
let value = node.name.as_deref().unwrap_or_else(|| node.get_id());
|
||||||
.name
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or_else(|| node.get_id());
|
|
||||||
|
|
||||||
let pixbuf = icon::get_icon(&icon_theme, node.get_id(), self.icon_size);
|
let pixbuf = icon::get_icon(&icon_theme, node.get_id(), self.icon_size);
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ use crate::collection::Collection;
|
|||||||
use crate::icon::{find_desktop_file, get_icon};
|
use crate::icon::{find_desktop_file, get_icon};
|
||||||
use crate::modules::launcher::popup::Popup;
|
use crate::modules::launcher::popup::Popup;
|
||||||
use crate::modules::launcher::FocusEvent;
|
use crate::modules::launcher::FocusEvent;
|
||||||
use crate::popup::PopupAlignment;
|
|
||||||
use crate::sway::SwayNode;
|
use crate::sway::SwayNode;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Image};
|
use gtk::{Button, IconTheme, Image};
|
||||||
@@ -175,19 +174,8 @@ impl LauncherItem {
|
|||||||
button.connect_enter_notify_event(move |button, _| {
|
button.connect_enter_notify_event(move |button, _| {
|
||||||
let windows = windows.lock().unwrap();
|
let windows = windows.lock().unwrap();
|
||||||
if windows.len() > 1 {
|
if windows.len() > 1 {
|
||||||
let button_w = button.allocation().width();
|
|
||||||
|
|
||||||
let (button_x, _) = button
|
|
||||||
.translate_coordinates(&button.toplevel().unwrap(), 0, 0)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let button_center = f64::from(button_x) + f64::from(button_w) / 2.0;
|
|
||||||
|
|
||||||
popup.set_windows(windows.as_slice(), &tx_hover);
|
popup.set_windows(windows.as_slice(), &tx_hover);
|
||||||
popup.show();
|
popup.show(button);
|
||||||
|
|
||||||
// TODO: Pass through module location
|
|
||||||
popup.set_pos(button_center, PopupAlignment::Center);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
let popup = Popup::new(
|
let popup = Popup::new(
|
||||||
"popup-launcher",
|
"popup-launcher",
|
||||||
info.app,
|
info.app,
|
||||||
|
info.monitor,
|
||||||
Orientation::Vertical,
|
Orientation::Vertical,
|
||||||
info.bar_position,
|
info.bar_position,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use crate::config::BarPosition;
|
|||||||
/// Shamelessly stolen from here:
|
/// Shamelessly stolen from here:
|
||||||
/// <https://github.com/zeroeightysix/rustbar/blob/master/src/modules/module.rs>
|
/// <https://github.com/zeroeightysix/rustbar/blob/master/src/modules/module.rs>
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
|
use gtk::gdk::Monitor;
|
||||||
use gtk::{Application, Widget};
|
use gtk::{Application, Widget};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
@@ -32,6 +33,7 @@ pub struct ModuleInfo<'a> {
|
|||||||
pub app: &'a Application,
|
pub app: &'a Application,
|
||||||
pub location: ModuleLocation,
|
pub location: ModuleLocation,
|
||||||
pub bar_position: &'a BarPosition,
|
pub bar_position: &'a BarPosition,
|
||||||
|
pub monitor: &'a Monitor,
|
||||||
pub output_name: &'a str,
|
pub output_name: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use self::popup::Popup;
|
|||||||
use crate::modules::mpd::client::{get_connection, get_duration, get_elapsed};
|
use crate::modules::mpd::client::{get_connection, get_duration, get_elapsed};
|
||||||
use crate::modules::mpd::popup::{MpdPopup, PopupEvent};
|
use crate::modules::mpd::popup::{MpdPopup, PopupEvent};
|
||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use crate::popup::PopupAlignment;
|
|
||||||
use dirs::home_dir;
|
use dirs::home_dir;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
@@ -80,7 +79,7 @@ fn get_tokens(re: &Regex, format_string: &str) -> Vec<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
Open(f64),
|
Open,
|
||||||
Update(Box<Option<(Song, Status, String)>>),
|
Update(Box<Option<(Song, Status, String)>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +95,7 @@ impl Module<Button> for MpdModule {
|
|||||||
let popup = Popup::new(
|
let popup = Popup::new(
|
||||||
"popup-mpd",
|
"popup-mpd",
|
||||||
info.app,
|
info.app,
|
||||||
|
info.monitor,
|
||||||
Orientation::Horizontal,
|
Orientation::Horizontal,
|
||||||
info.bar_position,
|
info.bar_position,
|
||||||
);
|
);
|
||||||
@@ -106,16 +106,8 @@ impl Module<Button> for MpdModule {
|
|||||||
|
|
||||||
let music_dir = self.music_dir.clone();
|
let music_dir = self.music_dir.clone();
|
||||||
|
|
||||||
button.connect_clicked(move |button| {
|
button.connect_clicked(move |_| {
|
||||||
let button_w = button.allocation().width();
|
click_tx.send(Event::Open).unwrap();
|
||||||
|
|
||||||
let (button_x, _) = button
|
|
||||||
.translate_coordinates(&button.toplevel().unwrap(), 0, 0)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
click_tx
|
|
||||||
.send(Event::Open(f64::from(button_x + button_w)))
|
|
||||||
.unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let host = self.host.clone();
|
let host = self.host.clone();
|
||||||
@@ -167,9 +159,8 @@ impl Module<Button> for MpdModule {
|
|||||||
|
|
||||||
rx.attach(None, move |event| {
|
rx.attach(None, move |event| {
|
||||||
match event {
|
match event {
|
||||||
Event::Open(pos) => {
|
Event::Open => {
|
||||||
mpd_popup.popup.show();
|
mpd_popup.popup.show(&button);
|
||||||
mpd_popup.popup.set_pos(pos, PopupAlignment::Right);
|
|
||||||
}
|
}
|
||||||
Event::Update(mut msg) => {
|
Event::Update(mut msg) => {
|
||||||
if let Some((song, status, string)) = msg.take() {
|
if let Some((song, status, string)) = msg.take() {
|
||||||
@@ -220,7 +211,7 @@ impl MpdModule {
|
|||||||
PlayState::Playing => self.icon_play.as_ref(),
|
PlayState::Playing => self.icon_play.as_ref(),
|
||||||
PlayState::Paused => self.icon_pause.as_ref(),
|
PlayState::Paused => self.icon_pause.as_ref(),
|
||||||
};
|
};
|
||||||
icon.map(|i| i.as_str())
|
icon.map(String::as_str)
|
||||||
}
|
}
|
||||||
"title" => song.title(),
|
"title" => song.title(),
|
||||||
"album" => try_get_first_tag(song.tags.get(&Tag::Album)),
|
"album" => try_get_first_tag(song.tags.get(&Tag::Album)),
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
let raw = sway.ipc(IpcCommand::GetWorkspaces).unwrap();
|
let raw = sway.ipc(IpcCommand::GetWorkspaces).unwrap();
|
||||||
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw).unwrap();
|
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw).unwrap();
|
||||||
|
|
||||||
if !self.all_monitors {
|
if self.all_monitors {
|
||||||
|
workspaces
|
||||||
|
} else {
|
||||||
workspaces
|
workspaces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|workspace| workspace.output == info.output_name)
|
.filter(|workspace| workspace.output == info.output_name)
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
|
||||||
workspaces
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
55
src/popup.rs
55
src/popup.rs
@@ -1,23 +1,20 @@
|
|||||||
use crate::config::BarPosition;
|
use crate::config::BarPosition;
|
||||||
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Orientation};
|
use gtk::{Application, ApplicationWindow, Button, Orientation};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Popup {
|
pub struct Popup {
|
||||||
pub window: ApplicationWindow,
|
pub window: ApplicationWindow,
|
||||||
pub container: gtk::Box,
|
pub container: gtk::Box,
|
||||||
}
|
monitor: Monitor,
|
||||||
|
|
||||||
pub enum PopupAlignment {
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Popup {
|
impl Popup {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: &str,
|
name: &str,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
|
monitor: &Monitor,
|
||||||
orientation: Orientation,
|
orientation: Orientation,
|
||||||
bar_position: &BarPosition,
|
bar_position: &BarPosition,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -72,11 +69,11 @@ impl Popup {
|
|||||||
win.add(&content);
|
win.add(&content);
|
||||||
|
|
||||||
win.connect_leave_notify_event(|win, ev| {
|
win.connect_leave_notify_event(|win, ev| {
|
||||||
|
const THRESHOLD: f64 = 3.0;
|
||||||
|
|
||||||
let (w, _h) = win.size();
|
let (w, _h) = win.size();
|
||||||
let (x, y) = ev.position();
|
let (x, y) = ev.position();
|
||||||
|
|
||||||
const THRESHOLD: f64 = 3.0;
|
|
||||||
|
|
||||||
// some child widgets trigger this event
|
// some child widgets trigger this event
|
||||||
// so check we're actually outside the window
|
// so check we're actually outside the window
|
||||||
if x < THRESHOLD || y < THRESHOLD || x > f64::from(w) - THRESHOLD {
|
if x < THRESHOLD || y < THRESHOLD || x > f64::from(w) - THRESHOLD {
|
||||||
@@ -89,29 +86,41 @@ impl Popup {
|
|||||||
Self {
|
Self {
|
||||||
window: win,
|
window: win,
|
||||||
container: content,
|
container: content,
|
||||||
|
monitor: monitor.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the popover's X position relative to the left border of the screen
|
|
||||||
pub fn set_pos(&self, pos: f64, alignment: PopupAlignment) {
|
|
||||||
let width = self.window.allocated_width();
|
|
||||||
|
|
||||||
let offset = match alignment {
|
|
||||||
PopupAlignment::Left => pos,
|
|
||||||
PopupAlignment::Center => (pos - (f64::from(width) / 2.0)).round(),
|
|
||||||
PopupAlignment::Right => pos - f64::from(width),
|
|
||||||
};
|
|
||||||
|
|
||||||
gtk_layer_shell::set_margin(&self.window, gtk_layer_shell::Edge::Left, offset as i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shows the popover
|
/// Shows the popover
|
||||||
pub fn show(&self) {
|
pub fn show(&self, button: &Button) {
|
||||||
self.window.show_all();
|
self.window.show_all();
|
||||||
|
self.set_pos(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hides the popover
|
/// Hides the popover
|
||||||
pub fn hide(&self) {
|
pub fn hide(&self) {
|
||||||
self.window.hide();
|
self.window.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the popover's X position relative to the left border of the screen
|
||||||
|
fn set_pos(&self, button: &Button) {
|
||||||
|
let widget_width = button.allocation().width();
|
||||||
|
let screen_width = self.monitor.workarea().width();
|
||||||
|
let popup_width = self.window.allocated_width();
|
||||||
|
|
||||||
|
let (widget_x, _) = button
|
||||||
|
.translate_coordinates(&button.toplevel().unwrap(), 0, 0)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let widget_center = f64::from(widget_x) + f64::from(widget_width) / 2.0;
|
||||||
|
|
||||||
|
let mut offset = (widget_center - (f64::from(popup_width) / 2.0)).round();
|
||||||
|
|
||||||
|
if offset < 5.0 {
|
||||||
|
offset = 5.0;
|
||||||
|
} else if offset > f64::from(screen_width - popup_width) - 5.0 {
|
||||||
|
offset = f64::from(screen_width - popup_width) - 5.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_layer_shell::set_margin(&self.window, gtk_layer_shell::Edge::Left, offset as i32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user