Установка: ярлык в «Пуске», .desktop для Linux, иконка в exe
- scripts/install-windows.ps1: exe в %LOCALAPPDATA%\Programs\Qulyp + ярлык (-Desktop, -Uninstall) - scripts/install-linux.sh: ~/.local/bin + .desktop + SVG-иконка (--uninstall) - build.rs вшивает иконку и описание в exe через winresource - Растр иконки вынесен в src/icon_pixels.rs (общий для окна и build.rs) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
55c98747e3
commit
54c484300f
@ -29,6 +29,7 @@
|
||||
- `src/storage.rs` — сессия: атомарная запись, `.bak`, автослияние внешних изменений
|
||||
- `src/app.rs` — экраны и UI; `src/widgets.rs`, `src/theme.rs` — виджеты и стиль
|
||||
- `src/devshots.rs` — dev-сценарий со скриншотами (feature `dev-shots`)
|
||||
- `build.rs` — вшивает иконку в exe; `scripts/` — установка для Windows и Linux
|
||||
|
||||
## Команды
|
||||
- `cargo test` — все тесты; `cargo run` — запуск; `cargo build --release` — релиз
|
||||
|
||||
41
Cargo.lock
generated
41
Cargo.lock
generated
@ -2244,6 +2244,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"sha2",
|
||||
"uuid",
|
||||
"winresource",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@ -2514,6 +2515,15 @@ dependencies = [
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.7"
|
||||
@ -2791,6 +2801,21 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "1.1.6+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "1.1.1+spec-1.1.0"
|
||||
@ -2821,6 +2846,12 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.1.2+spec-1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
@ -3631,6 +3662,16 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winresource"
|
||||
version = "0.1.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087"
|
||||
dependencies = [
|
||||
"toml",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
|
||||
@ -37,6 +37,9 @@ strip = true
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
arboard = { version = "3.6", default-features = false, features = ["wayland-data-control"] }
|
||||
|
||||
[build-dependencies]
|
||||
winresource = "0.1"
|
||||
|
||||
[dependencies.png]
|
||||
version = "0.17"
|
||||
optional = true
|
||||
|
||||
17
README.md
17
README.md
@ -31,7 +31,22 @@ Qulyp каждые 2 секунды проверяет, не изменился
|
||||
| `Ctrl+L` | заблокировать |
|
||||
| `↑` `↓`, `Del`, `Esc` | навигация, удаление, отмена |
|
||||
|
||||
## Сборка
|
||||
## Установка
|
||||
Windows (без прав администратора; exe → `%LOCALAPPDATA%Programsulyp\`\,\ ярлык\ в\ \«Пуске\»\)\:\
|
||||
\
|
||||
\`\`\`bash\
|
||||
powershell\ \-ExecutionPolicy\ Bypass\ \-File\ scripts\/install\-windows\.ps1\
|
||||
\`\`\`\
|
||||
\
|
||||
Флаги\:\ \`\-Desktop\`\ \—\ ещё\ ярлык\ на\ рабочем\ столе\,\ \`\-Uninstall\`\ \—\ удалить\.\ Linux\ \(\`\~\/\.local\/bin\`\ \+\ пункт\ в\ меню\ приложений\)\:\
|
||||
\
|
||||
\`\`\`bash\
|
||||
sh\ scripts\/install\-linux\.sh\
|
||||
\`\`\`\
|
||||
\
|
||||
Удаление\ \—\ \`\-\-uninstall\`\.\ Файлы\ хранилищ\ при\ удалении\ не\ трогаются\.\ Для\ обновления\ запустите\ скрипт\ ещё\ раз\.\
|
||||
\
|
||||
\#\#\ Сборка\ вручную\
|
||||
Нужен Rust 1.85+ (<https://rustup.rs>).
|
||||
|
||||
```bash
|
||||
|
||||
7
assets/qulyp.svg
Normal file
7
assets/qulyp.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||
<rect x="2" y="2" width="60" height="60" rx="10" fill="#446FB0"/>
|
||||
<path d="M21.5 32 V27 a10.5 10.5 0 0 1 21 0 V32" fill="none" stroke="#fff" stroke-width="4"/>
|
||||
<rect x="16" y="30" width="32" height="23" rx="4" fill="#fff"/>
|
||||
<circle cx="32" cy="39.5" r="3.4" fill="#446FB0"/>
|
||||
<rect x="30.4" y="39.5" width="3.2" height="7" fill="#446FB0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 440 B |
71
build.rs
Normal file
71
build.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! На Windows вшивает в exe иконку и описание — их видят проводник, «Пуск» и поиск.
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[path = "src/icon_pixels.rs"]
|
||||
mod icon_pixels;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=src/icon_pixels.rs");
|
||||
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
|
||||
return;
|
||||
}
|
||||
let ico = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("qulyp.ico");
|
||||
std::fs::write(&ico, make_ico(&[16, 24, 32, 48, 64, 256])).expect("write ico");
|
||||
|
||||
let mut res = winresource::WindowsResource::new();
|
||||
res.set_icon(ico.to_str().unwrap())
|
||||
.set("ProductName", "Qulyp")
|
||||
.set("FileDescription", "Qulyp — менеджер паролей")
|
||||
.set("LegalCopyright", "MIT");
|
||||
if let Err(e) = res.compile() {
|
||||
// Без Windows SDK exe всё равно соберётся, просто без иконки.
|
||||
println!("cargo:warning=иконка не вшита: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// ICO из нескольких 32-битных BMP-образов.
|
||||
fn make_ico(sizes: &[usize]) -> Vec<u8> {
|
||||
let images: Vec<Vec<u8>> = sizes.iter().map(|&n| bmp_image(n)).collect();
|
||||
let mut out = Vec::new();
|
||||
out.extend_from_slice(&[0, 0, 1, 0]);
|
||||
out.extend_from_slice(&(sizes.len() as u16).to_le_bytes());
|
||||
let mut offset = 6 + 16 * sizes.len();
|
||||
for (&n, img) in sizes.iter().zip(&images) {
|
||||
let b = if n >= 256 { 0 } else { n as u8 };
|
||||
out.extend_from_slice(&[b, b, 0, 0]);
|
||||
out.extend_from_slice(&1u16.to_le_bytes()); // planes
|
||||
out.extend_from_slice(&32u16.to_le_bytes()); // bpp
|
||||
out.extend_from_slice(&(img.len() as u32).to_le_bytes());
|
||||
out.extend_from_slice(&(offset as u32).to_le_bytes());
|
||||
offset += img.len();
|
||||
}
|
||||
for img in images {
|
||||
out.extend_from_slice(&img);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
fn bmp_image(n: usize) -> Vec<u8> {
|
||||
let rgba = icon_pixels::rgba(n);
|
||||
let mask_row = n.div_ceil(32) * 4;
|
||||
let mut out = Vec::new();
|
||||
// BITMAPINFOHEADER: высота удвоена (XOR-картинка + AND-маска)
|
||||
for v in [40u32, n as u32, (n * 2) as u32] {
|
||||
out.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
out.extend_from_slice(&1u16.to_le_bytes());
|
||||
out.extend_from_slice(&32u16.to_le_bytes());
|
||||
for v in [0u32, (n * n * 4 + mask_row * n) as u32, 0, 0, 0, 0] {
|
||||
out.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
for y in (0..n).rev() {
|
||||
for x in 0..n {
|
||||
let i = (y * n + x) * 4;
|
||||
out.extend_from_slice(&[rgba[i + 2], rgba[i + 1], rgba[i], rgba[i + 3]]);
|
||||
}
|
||||
}
|
||||
out.extend(std::iter::repeat_n(0u8, mask_row * n)); // прозрачность берётся из альфы
|
||||
out
|
||||
}
|
||||
@ -1,6 +1,10 @@
|
||||
<!-- limit: 80 -->
|
||||
# Журнал (новые сверху)
|
||||
|
||||
## 2026-09-30 (установка)
|
||||
- Скрипты установки scripts/install-windows.ps1 (ярлык в «Пуске») и install-linux.sh (.desktop)
|
||||
- Иконка и описание вшиты в exe (build.rs + winresource), общий растр в src/icon_pixels.rs
|
||||
|
||||
## 2026-09-30 (дизайн)
|
||||
- Строгий стиль: графитовая палитра, один приглушённый акцент, скругления 4–6 px, без теней на панелях
|
||||
- Свои векторные иконки (src/icons.rs) вместо эмодзи; нейтральные монограммы вместо цветных аватаров
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
## Дальше
|
||||
- [ ] Импорт/экспорт CSV (форматы Bitwarden, Chrome, KeePass)
|
||||
- [ ] Сборка дистрибутивов: .msi/.exe-установщик, AppImage/.deb
|
||||
- [ ] Дистрибутивы: .msi-установщик, AppImage/.deb (пока — скрипты в scripts/)
|
||||
- [ ] Ассоциация файлов `.qulyp` (двойной клик открывает хранилище)
|
||||
- [ ] Автоподхват конфликтных копий облака (`… (conflicted copy).qulyp`)
|
||||
|
||||
|
||||
48
scripts/install-linux.sh
Executable file
48
scripts/install-linux.sh
Executable file
@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env sh
|
||||
# Установка Qulyp для текущего пользователя (без sudo):
|
||||
# бинарник -> ~/.local/bin/qulyp
|
||||
# ярлык -> ~/.local/share/applications/qulyp.desktop (виден в меню и поиске приложений)
|
||||
# иконка -> ~/.local/share/icons/hicolor/scalable/apps/qulyp.svg
|
||||
#
|
||||
# sh scripts/install-linux.sh # установить / обновить
|
||||
# sh scripts/install-linux.sh --uninstall # удалить (хранилища .qulyp не трогаются)
|
||||
set -eu
|
||||
|
||||
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||
bin="$HOME/.local/bin"
|
||||
apps="$HOME/.local/share/applications"
|
||||
icons="$HOME/.local/share/icons/hicolor/scalable/apps"
|
||||
|
||||
if [ "${1:-}" = "--uninstall" ]; then
|
||||
rm -f "$bin/qulyp" "$apps/qulyp.desktop" "$icons/qulyp.svg"
|
||||
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" || true
|
||||
echo "Qulyp удалён. Файлы хранилищ (.qulyp) и настройки не тронуты."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Сборка release…"
|
||||
cargo build --release --manifest-path "$root/Cargo.toml"
|
||||
|
||||
mkdir -p "$bin" "$apps" "$icons"
|
||||
install -m 755 "$root/target/release/qulyp" "$bin/qulyp"
|
||||
install -m 644 "$root/assets/qulyp.svg" "$icons/qulyp.svg"
|
||||
|
||||
cat > "$apps/qulyp.desktop" <<EOF
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Qulyp
|
||||
GenericName=Менеджер паролей
|
||||
Comment=Локальный менеджер паролей
|
||||
Exec=$bin/qulyp %f
|
||||
Icon=qulyp
|
||||
Terminal=false
|
||||
Categories=Utility;Security;
|
||||
Keywords=password;passwords;keepass;пароль;пароли;
|
||||
StartupWMClass=qulyp
|
||||
EOF
|
||||
|
||||
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database "$apps" || true
|
||||
command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q "$HOME/.local/share/icons/hicolor" || true
|
||||
|
||||
echo "Готово: $bin/qulyp"
|
||||
echo "Найти программу: меню приложений -> поиск «Qulyp»."
|
||||
48
scripts/install-windows.ps1
Normal file
48
scripts/install-windows.ps1
Normal file
@ -0,0 +1,48 @@
|
||||
# Установка Qulyp для текущего пользователя (права администратора не нужны):
|
||||
# exe -> %LOCALAPPDATA%\Programs\Qulyp\qulyp.exe
|
||||
# ярлык в меню «Пуск» -> находится поиском Windows по слову «Qulyp»
|
||||
#
|
||||
# powershell -ExecutionPolicy Bypass -File scripts\install-windows.ps1 # установить / обновить
|
||||
# powershell -ExecutionPolicy Bypass -File scripts\install-windows.ps1 -Desktop # + ярлык на рабочем столе
|
||||
# powershell -ExecutionPolicy Bypass -File scripts\install-windows.ps1 -Uninstall # удалить программу (хранилища не трогаются)
|
||||
param([switch]$Desktop, [switch]$Uninstall)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$dest = Join-Path $env:LOCALAPPDATA 'Programs\Qulyp'
|
||||
$startLnk = Join-Path ([Environment]::GetFolderPath('Programs')) 'Qulyp.lnk'
|
||||
$deskLnk = Join-Path ([Environment]::GetFolderPath('Desktop')) 'Qulyp.lnk'
|
||||
|
||||
if ($Uninstall) {
|
||||
Get-Process qulyp -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "$dest*" } | Stop-Process -Force
|
||||
foreach ($p in @($startLnk, $deskLnk)) { if (Test-Path $p) { Remove-Item $p -Force } }
|
||||
if (Test-Path $dest) { Remove-Item $dest -Recurse -Force }
|
||||
Write-Host 'Qulyp удалён. Файлы хранилищ (.qulyp) и настройки не тронуты.'
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host 'Сборка release…'
|
||||
cargo build --release --manifest-path (Join-Path $root 'Cargo.toml')
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Сборка не удалась' }
|
||||
|
||||
$running = Get-Process qulyp -ErrorAction SilentlyContinue | Where-Object { $_.Path -like "$dest*" }
|
||||
if ($running) { throw 'Закройте Qulyp перед обновлением' }
|
||||
|
||||
New-Item -ItemType Directory -Force $dest | Out-Null
|
||||
$exe = Join-Path $dest 'qulyp.exe'
|
||||
Copy-Item (Join-Path $root 'target\release\qulyp.exe') $exe -Force
|
||||
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
$links = @($startLnk)
|
||||
if ($Desktop) { $links += $deskLnk }
|
||||
foreach ($path in $links) {
|
||||
$lnk = $shell.CreateShortcut($path)
|
||||
$lnk.TargetPath = $exe
|
||||
$lnk.WorkingDirectory = $dest
|
||||
$lnk.IconLocation = "$exe,0"
|
||||
$lnk.Description = 'Qulyp — менеджер паролей'
|
||||
$lnk.Save()
|
||||
}
|
||||
|
||||
Write-Host "Готово: $exe"
|
||||
Write-Host 'Найти программу: Пуск -> поиск «Qulyp».'
|
||||
59
src/icon_pixels.rs
Normal file
59
src/icon_pixels.rs
Normal file
@ -0,0 +1,59 @@
|
||||
//! Растровая иконка приложения (квадрат с замком). Общий код для окна
|
||||
//! (`theme::app_icon`) и `build.rs`, который вшивает .ico в exe на Windows.
|
||||
|
||||
/// Цвет фона иконки — акцент интерфейса.
|
||||
pub const BG: [u8; 3] = [0x44, 0x6F, 0xB0];
|
||||
|
||||
fn rrect(x: f32, y: f32, x0: f32, y0: f32, x1: f32, y1: f32, r: f32) -> bool {
|
||||
let cx = x.clamp(x0 + r, x1 - r);
|
||||
let cy = y.clamp(y0 + r, y1 - r);
|
||||
(x - cx).powi(2) + (y - cy).powi(2) <= r * r
|
||||
}
|
||||
|
||||
/// Что находится в точке (координаты в сетке 64×64): 0 — пусто, 1 — фон, 2 — замок.
|
||||
fn sample(x: f32, y: f32) -> u8 {
|
||||
if !rrect(x, y, 2.0, 2.0, 62.0, 62.0, 10.0) {
|
||||
return 0;
|
||||
}
|
||||
let d = ((x - 32.0).powi(2) + (y - 27.0).powi(2)).sqrt();
|
||||
let arc = y <= 27.0 && (8.5..=12.5).contains(&d);
|
||||
let legs = ((19.5..=23.5).contains(&x) || (40.5..=44.5).contains(&x)) && (27.0..=32.0).contains(&y);
|
||||
let body = rrect(x, y, 16.0, 30.0, 48.0, 53.0, 4.0);
|
||||
let hole = ((x - 32.0).powi(2) + (y - 39.5).powi(2)).sqrt() <= 3.4
|
||||
|| ((30.4..=33.6).contains(&x) && (39.5..=46.5).contains(&y));
|
||||
if (arc || legs || body) && !hole { 2 } else { 1 }
|
||||
}
|
||||
|
||||
/// RGBA-пиксели иконки размера `n×n` со сглаживанием 4×4.
|
||||
pub fn rgba(n: usize) -> Vec<u8> {
|
||||
const SS: usize = 4;
|
||||
let mut out = vec![0u8; n * n * 4];
|
||||
let k = 64.0 / n as f32;
|
||||
for py in 0..n {
|
||||
for px in 0..n {
|
||||
let (mut bg, mut fg) = (0u32, 0u32);
|
||||
for sy in 0..SS {
|
||||
for sx in 0..SS {
|
||||
let x = (px as f32 + (sx as f32 + 0.5) / SS as f32) * k;
|
||||
let y = (py as f32 + (sy as f32 + 0.5) / SS as f32) * k;
|
||||
match sample(x, y) {
|
||||
1 => bg += 1,
|
||||
2 => fg += 1,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let covered = bg + fg;
|
||||
if covered == 0 {
|
||||
continue;
|
||||
}
|
||||
let t = fg as f32 / covered as f32; // доля белого внутри покрытой области
|
||||
let i = (py * n + px) * 4;
|
||||
for c in 0..3 {
|
||||
out[i + c] = (BG[c] as f32 * (1.0 - t) + 255.0 * t).round() as u8;
|
||||
}
|
||||
out[i + 3] = (255 * covered / (SS * SS) as u32) as u8;
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
@ -7,6 +7,7 @@ mod crypto;
|
||||
#[cfg(feature = "dev-shots")]
|
||||
mod devshots;
|
||||
mod generator;
|
||||
mod icon_pixels;
|
||||
mod icons;
|
||||
mod storage;
|
||||
mod theme;
|
||||
|
||||
34
src/theme.rs
34
src/theme.rs
@ -210,38 +210,8 @@ fn install_fonts(ctx: &egui::Context) {
|
||||
ctx.set_fonts(fonts);
|
||||
}
|
||||
|
||||
/// Иконка окна: скруглённый квадрат с замком, рисуется попиксельно.
|
||||
/// Иконка окна (та же, что вшита в exe).
|
||||
pub fn app_icon() -> IconData {
|
||||
const N: usize = 64;
|
||||
let mut rgba = vec![0u8; N * N * 4];
|
||||
let inside_rrect = |x: f32, y: f32, x0: f32, y0: f32, x1: f32, y1: f32, r: f32| {
|
||||
let cx = x.clamp(x0 + r, x1 - r);
|
||||
let cy = y.clamp(y0 + r, y1 - r);
|
||||
(x - cx).powi(2) + (y - cy).powi(2) <= r * r
|
||||
};
|
||||
for py in 0..N {
|
||||
for px in 0..N {
|
||||
let (x, y) = (px as f32 + 0.5, py as f32 + 0.5);
|
||||
let i = (py * N + px) * 4;
|
||||
if !inside_rrect(x, y, 2.0, 2.0, 62.0, 62.0, 10.0) {
|
||||
continue;
|
||||
}
|
||||
let [r, g, b, _] = ACCENT.to_array();
|
||||
let mut c = [r, g, b, 255];
|
||||
// Дужка замка
|
||||
let d = ((x - 32.0).powi(2) + (y - 27.0).powi(2)).sqrt();
|
||||
let shackle = y <= 30.0 && (8.5..=13.0).contains(&d);
|
||||
let legs = (19.0..=23.5).contains(&x) || (40.5..=45.0).contains(&x);
|
||||
let shackle = shackle || (legs && (27.0..=32.0).contains(&y));
|
||||
// Корпус с замочной скважиной
|
||||
let body = inside_rrect(x, y, 15.0, 30.0, 49.0, 54.0, 5.0);
|
||||
let hole = ((x - 32.0).powi(2) + (y - 39.5).powi(2)).sqrt() <= 3.5
|
||||
|| ((30.5..=33.5).contains(&x) && (39.5..=47.0).contains(&y));
|
||||
if (shackle || body) && !hole {
|
||||
c = [255, 255, 255, 255];
|
||||
}
|
||||
rgba[i..i + 4].copy_from_slice(&c);
|
||||
}
|
||||
}
|
||||
IconData { rgba, width: N as u32, height: N as u32 }
|
||||
IconData { rgba: crate::icon_pixels::rgba(N), width: N as u32, height: N as u32 }
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user