7 Commits
10 changed files with 292 additions and 64 deletions
+6 -1
View File
@@ -1,6 +1,11 @@
# yume4kde
Yume Nikki desktop pet for Wayland KDE
Yume Nikki desktop pet ported for Wayland KDE
![preview](preview.gif)
## Credits
[Sierpinski Simon, Yume Nikki Desktop Pet for Windows](https://sierpinski-simon.itch.io/yume-nikki-desktop-pet)
## Install
+1 -1
View File
@@ -44,7 +44,7 @@
"install -Dm644 packaging/jp.angeltech.yume4kde.png /app/share/icons/hicolor/32x32/apps/jp.angeltech.yume4kde.png"
],
"sources": [
{"type": "archive", "url": "https://git.acid.im/Angel-Technologies/yume4kde/archive/v0.0.1.tar.gz", "sha256": "5afe541fbd78fd29e60e70bbb74e27c4610c60cb4de27b2d7f4bc16725a46da4"},
{"type": "archive", "url": "https://git.acid.im/Angel-Technologies/yume4kde/archive/v0.0.3.tar.gz", "sha256": "65e43e2c0ea638b39d0a4fca37edaa0086eca5c37474936fbade0ab8642131a0"},
"packaging/cargo-sources.json"
]
}
+27
View File
@@ -11,8 +11,10 @@
#include <QFile>
#include <QGuiApplication>
#include <QScreen>
#include <QSettings>
#include <QtQml/qqml.h>
#include <algorithm>
#include <cmath>
namespace {
constexpr auto scriptName = "jp.angeltech.yume4kde.WindowSurfaces";
@@ -31,6 +33,31 @@ void yume4kde::prepare_application()
qputenv("QT_QUICK_CONTROLS_STYLE", "Basic");
}
double yume4kde::load_size_factor()
{
QSettings settings(QSettings::IniFormat, QSettings::UserScope,
QStringLiteral("jp.angeltech"), QStringLiteral("yume4kde"));
bool ok = false;
const double factor = settings.value(QStringLiteral("size_factor"), 1.0).toDouble(&ok);
if (!ok || !std::isfinite(factor))
return 1.0;
return factor;
}
void yume4kde::save_size_factor(double factor)
{
if (!std::isfinite(factor)) {
qWarning() << "Cannot save non-finite size factor.";
return;
}
QSettings settings(QSettings::IniFormat, QSettings::UserScope,
QStringLiteral("jp.angeltech"), QStringLiteral("yume4kde"));
settings.setValue(QStringLiteral("size_factor"), factor);
settings.sync();
if (settings.status() != QSettings::NoError)
qWarning() << "Failed to save size factor:" << settings.status();
}
void yume4kde::register_platform_types()
{
QGuiApplication::setDesktopFileName(applicationService);
+2
View File
@@ -16,6 +16,8 @@ class QScreen;
namespace yume4kde {
void prepare_application();
void register_platform_types();
double load_size_factor();
void save_size_factor(double factor);
}
class DesktopIntegration : public QObject
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+5 -5
View File
@@ -14,6 +14,7 @@ Window {
height: desktop.screenHeight
property int selectedActorId: -1
property real wheelRemainder: 0
DesktopIntegration {
id: desktop
@@ -117,7 +118,6 @@ Window {
property bool dragged: false
property point pressPosition
property point pressOffset
property real wheelRemainder: 0
onPressed: function(mouse) {
const p = mapToItem(root.contentItem, mouse.x, mouse.y)
if (!desktop.hitTest(actorDelegate.modelData.imageSource, mouse.x, mouse.y, width, height, actorDelegate.modelData.mirrored)) {
@@ -172,11 +172,11 @@ Window {
wheel.accepted = false
return
}
wheelRemainder += wheel.angleDelta.y
const steps = Math.trunc(wheelRemainder / 120)
root.wheelRemainder += wheel.angleDelta.y
const steps = Math.trunc(root.wheelRemainder / 120)
if (steps !== 0) {
wheelRemainder -= steps * 120
root.backend.resizeActor(actorDelegate.actorId, steps)
root.wheelRemainder -= steps * 120
root.backend.resizeCharacters(steps)
}
}
}
+7 -5
View File
@@ -306,10 +306,12 @@ impl Backend {
self.world.end_drag(id);
self.synchronize();
}
#[qslot(qml_name = "resizeActor")]
fn resize_actor(&mut self, id: i32, steps: f64) {
self.world.resize_pet(id, steps);
self.synchronize();
#[qslot(qml_name = "resizeCharacters")]
fn resize_characters(&mut self, steps: f64) {
if self.world.resize_pets(steps) {
crate::platform::save_size_factor(self.world.size_factor());
self.synchronize();
}
}
#[qslot(qml_name = "openMenu")]
fn open_menu(&mut self, id: i32) {
@@ -361,7 +363,7 @@ impl Backend {
actors: Vec::new(),
decorations: Vec::new(),
visible: false,
world: World::default(),
world: World::with_size_factor(crate::platform::load_size_factor()),
last_advance: Instant::now(),
exit_requested,
}
+2
View File
@@ -16,6 +16,8 @@ mod platform {
include!("native/platform.hpp");
fn prepare_application();
fn register_platform_types();
fn load_size_factor() -> f64;
fn save_size_factor(factor: f64);
}
}
+89 -36
View File
@@ -6,6 +6,7 @@ mod climbing;
pub use climbing::ClimbState;
pub const SPRITE_SCALE: f64 = 3.0;
const REFERENCE_SCREEN_HEIGHT: f64 = 1080.0;
const WALK_SPEED: f64 = 2.0 / 0.03;
const GRAVITY: f64 = 900.0;
const SNAP_DISTANCE: f64 = 30.0;
@@ -213,7 +214,6 @@ pub struct PetState {
pub sleeping: bool,
pub support: Option<Support>,
pub mirrored: bool,
requested_scale: f64,
direction: Direction,
motion: HorizontalMotion,
velocity_y: f64,
@@ -233,7 +233,6 @@ impl PetState {
x,
y,
scale: SPRITE_SCALE,
requested_scale: SPRITE_SCALE,
dragging: false,
sleeping: false,
support: None,
@@ -350,6 +349,8 @@ pub struct World {
pub quit_requested: bool,
pub sounds: Vec<&'static str>,
pub quitting: Option<f64>,
screen_scale: f64,
requested_scale: f64,
rng: StdRng,
next_id: i32,
menu_actor: Option<i32>,
@@ -378,6 +379,8 @@ impl World {
quit_requested: false,
sounds: Vec::with_capacity(8),
quitting: None,
screen_scale: 1.0,
requested_scale: SPRITE_SCALE,
rng,
next_id: 2,
menu_actor: None,
@@ -386,9 +389,19 @@ impl World {
flute_remaining: 0.0,
}
}
pub fn with_size_factor(factor: f64) -> Self {
let mut world = Self::with_rng(StdRng::from_os_rng());
world.requested_scale = normalize_size_factor(factor) * SPRITE_SCALE;
world
}
pub fn size_factor(&self) -> f64 {
self.requested_scale / SPRITE_SCALE
}
pub fn update_desktop(&mut self, snapshot: DesktopSnapshot) {
let first = self.pets.is_empty();
self.screen_scale = (snapshot.screen.height / REFERENCE_SCREEN_HEIGHT).clamp(0.5, 2.0);
self.bounds = snapshot
.work_area
.intersection(snapshot.screen)
@@ -422,7 +435,13 @@ impl World {
self.pets.push(pet);
}
for pet in &mut self.pets {
fit_scale(pet, self.bounds);
fit_scale(
pet,
self.bounds,
self.screen_scale,
self.requested_scale,
self.sitting,
);
reconcile_pet(
pet,
self.sitting,
@@ -512,35 +531,25 @@ impl World {
}
}
pub fn resize_pet(&mut self, id: i32, steps: f64) {
if self.quitting.is_some() {
return;
pub fn resize_pets(&mut self, steps: f64) -> bool {
if self.quitting.is_some() || !steps.is_finite() {
return false;
}
let requested_scale =
(self.requested_scale + steps * 0.25).clamp(SPRITE_SCALE / 2.0, SPRITE_SCALE * 2.0);
if requested_scale == self.requested_scale {
return false;
}
self.requested_scale = requested_scale;
self.idle_elapsed = 0.0;
if let Some(pet) = self.pets.iter_mut().find(|pet| pet.id == id) {
let requested_scale =
(pet.requested_scale + steps * 0.25).clamp(SPRITE_SCALE / 2.0, SPRITE_SCALE * 2.0);
if requested_scale == pet.requested_scale {
return;
}
let previous_scale = pet.scale;
let (previous_width, _) = pet.size(self.sitting, None);
let previous_anchor = pet.attachment_height(self.sitting);
pet.requested_scale = requested_scale;
fit_scale(pet, self.bounds);
if pet.scale == previous_scale {
return;
}
let ratio = pet.scale / previous_scale;
if pet.dragging {
let (offset_x, offset_y) = pet.drag_offset;
pet.drag_offset = (offset_x * ratio, offset_y * ratio);
pet.x += offset_x - pet.drag_offset.0;
pet.y += offset_y - pet.drag_offset.1;
} else {
pet.x += previous_width * (1.0 - ratio) / 2.0;
pet.y += previous_anchor * (1.0 - ratio);
}
for pet in &mut self.pets {
fit_scale(
pet,
self.bounds,
self.screen_scale,
self.requested_scale,
self.sitting,
);
reconcile_pet(
pet,
self.sitting,
@@ -551,6 +560,7 @@ impl World {
self.bounds,
);
}
true
}
pub fn open_menu(&mut self, id: i32) {
@@ -600,7 +610,13 @@ impl World {
self.bounds.y + self.rng.random_range(0.1..0.7) * self.bounds.height,
);
self.next_id += 1;
fit_scale(&mut pet, self.bounds);
fit_scale(
&mut pet,
self.bounds,
self.screen_scale,
self.requested_scale,
false,
);
let (width, height) = pet.size(false, None);
(pet.x, pet.y) = constrain_position(self.bounds, &self.panels, pet.x, pet.y, width, height);
self.pets.push(pet);
@@ -655,9 +671,17 @@ impl World {
velocity_y: 0.0,
});
self.next_id += 1;
fit_scale(main, self.bounds);
}
for pet in &mut self.pets {
fit_scale(
pet,
self.bounds,
self.screen_scale,
self.requested_scale,
self.sitting,
);
reconcile_pet(
main,
pet,
self.sitting,
self.quitting,
&self.surfaces,
@@ -955,13 +979,42 @@ impl World {
}
}
fn fit_scale(pet: &mut PetState, bounds: Rectangle) {
fn normalize_size_factor(factor: f64) -> f64 {
if factor.is_finite() {
factor.clamp(0.5, 2.0)
} else {
1.0
}
}
fn fit_scale(
pet: &mut PetState,
bounds: Rectangle,
screen_scale: f64,
requested_scale: f64,
sitting: bool,
) {
let previous_scale = pet.scale;
let (previous_width, _) = pet.size(sitting, None);
let previous_anchor = pet.attachment_height(sitting);
let image = pet.image(false, None);
pet.scale = pet
.requested_scale
pet.scale = (screen_scale * requested_scale)
.min(bounds.width / image.width)
.min(bounds.height / image.height)
.max(0.01);
if pet.scale == previous_scale {
return;
}
let ratio = pet.scale / previous_scale;
if pet.dragging {
let (offset_x, offset_y) = pet.drag_offset;
pet.drag_offset = (offset_x * ratio, offset_y * ratio);
pet.x += offset_x - pet.drag_offset.0;
pet.y += offset_y - pet.drag_offset.1;
} else {
pet.x += previous_width * (1.0 - ratio) / 2.0;
pet.y += previous_anchor * (1.0 - ratio);
}
}
pub fn visible_surfaces(windows: &[WindowRecord], bounds: Rectangle) -> Vec<Surface> {
+153 -16
View File
@@ -11,7 +11,7 @@ fn rectangle(x: f64, y: f64, width: f64, height: f64) -> Rectangle {
fn snapshot(windows: Vec<WindowRecord>) -> DesktopSnapshot {
DesktopSnapshot {
screen: rectangle(0.0, 0.0, 1280.0, 800.0),
screen: rectangle(0.0, 0.0, 1280.0, 1080.0),
work_area: rectangle(40.0, 24.0, 1240.0, 728.0),
windows,
panels: vec![rectangle(0.0, 752.0, 1280.0, 48.0)],
@@ -470,20 +470,106 @@ fn bicycle_doubles_vertical_climbing_speed() {
}
}
#[test]
fn initial_size_tracks_screen_height_and_not_panel_size() {
for (screen_width, screen_height, expected_size) in [
(1280.0, 720.0, (48.0, 64.0)),
(1920.0, 1080.0, (72.0, 96.0)),
(3840.0, 2160.0, (144.0, 192.0)),
] {
let mut desktop = snapshot(Vec::new());
desktop.screen = rectangle(0.0, 0.0, screen_width, screen_height);
desktop.work_area = desktop.screen;
desktop.panels.clear();
let mut world = World::with_rng(StdRng::seed_from_u64(9));
world.update_desktop(desktop);
assert_eq!(world.pets[0].size(false, None), expected_size);
let mut desktop = snapshot(Vec::new());
desktop.screen = rectangle(0.0, 0.0, screen_width, screen_height);
desktop.work_area = rectangle(0.0, 0.0, screen_width, screen_height - 48.0);
desktop.panels.clear();
world.update_desktop(desktop);
assert_eq!(world.pets[0].size(false, None), expected_size);
}
}
#[test]
fn wheel_limits_follow_the_screen_relative_default() {
let mut desktop = snapshot(Vec::new());
desktop.screen = rectangle(0.0, 0.0, 1280.0, 720.0);
desktop.work_area = desktop.screen;
desktop.panels.clear();
let mut world = World::with_rng(StdRng::seed_from_u64(9));
world.update_desktop(desktop);
for _ in 0..6 {
world.resize_pets(-1.0);
}
assert_eq!(world.size_factor(), 0.5);
assert_eq!(world.pets[0].size(false, None), (24.0, 32.0));
for _ in 0..18 {
world.resize_pets(1.0);
}
assert_eq!(world.size_factor(), 2.0);
assert_eq!(world.pets[0].size(false, None), (96.0, 128.0));
}
#[test]
fn screen_resize_preserves_user_scale_and_drag_grip() {
let mut world = world();
world.pets[0].x = 300.0;
world.pets[0].y = 350.0;
let cursor = (318.0, 380.0);
world.begin_drag(1, cursor.0, cursor.1);
world.resize_pets(6.0);
assert_eq!(world.pets[0].size(false, None), (108.0, 144.0));
let mut desktop = snapshot(Vec::new());
desktop.screen.height = 720.0;
world.update_desktop(desktop);
let pet = &world.pets[0];
assert_eq!(pet.size(false, None), (72.0, 96.0));
assert_eq!(
(pet.x + pet.drag_offset.0, pet.y + pet.drag_offset.1),
cursor
);
world.update_desktop(snapshot(Vec::new()));
let pet = &world.pets[0];
assert_eq!(pet.size(false, None), (108.0, 144.0));
assert_eq!(
(pet.x + pet.drag_offset.0, pet.y + pet.drag_offset.1),
cursor
);
}
#[test]
fn screen_resize_keeps_the_pet_on_its_titlebar() {
let mut world = world();
attach_to_target(&mut world);
let mut desktop = snapshot(vec![window("target", 100.0, 320.0, 800.0, 1)]);
desktop.screen.height = 720.0;
world.update_desktop(desktop);
let pet = &world.pets[0];
assert_eq!(pet.size(false, None), (48.0, 64.0));
assert_eq!(pet.y + pet.attachment_height(false), 320.0);
assert!(matches!(&pet.support, Some(Support::Window { id, .. }) if id == "target"));
}
#[test]
fn resize_limits_survive_desktop_and_effect_changes() {
let mut world = world();
world.resize_pet(1, 1000.0);
world.resize_pets(1000.0);
assert_eq!(world.pets[0].size(false, None), (144.0, 192.0));
world.resize_pet(1, 1.0);
world.resize_pets(1.0);
assert_eq!(world.pets[0].size(false, None), (144.0, 192.0));
world.update_desktop(snapshot(Vec::new()));
world.set_effect(Effect::Bicycle);
world.set_effect(Effect::None);
assert_eq!(world.pets[0].size(false, None), (144.0, 192.0));
world.resize_pet(1, -1000.0);
world.resize_pets(-1000.0);
assert_eq!(world.pets[0].size(false, None), (36.0, 48.0));
world.resize_pet(1, -1.0);
world.resize_pets(-1.0);
assert_eq!(world.pets[0].size(false, None), (36.0, 48.0));
}
@@ -498,7 +584,7 @@ fn growing_on_the_floor_keeps_clear_of_panels_and_screen_edges() {
pet.x = 300.0;
pet.y = 656.0;
pet.support = Some(Support::Floor);
world.resize_pet(1, 1000.0);
world.resize_pets(1000.0);
let pet = &world.pets[0];
let (width, height) = pet.size(false, None);
assert_eq!(pet.y + height, 752.0);
@@ -511,12 +597,12 @@ fn growing_on_the_floor_keeps_clear_of_panels_and_screen_edges() {
fn resizing_preserves_titlebar_feet_and_seat_anchors() {
let mut world = world();
attach_to_target(&mut world);
world.resize_pet(1, 4.0);
world.resize_pets(4.0);
let pet = &world.pets[0];
assert_eq!(pet.y + pet.attachment_height(false), 320.0);
assert!(matches!(&pet.support, Some(Support::Window { id, .. }) if id == "target"));
world.toggle_sitting();
world.resize_pet(1, 4.0);
world.resize_pets(4.0);
let pet = &world.pets[0];
assert_eq!(pet.y + pet.attachment_height(true), 320.0);
assert!(pet.x >= 100.0 && pet.x + pet.size(true, None).0 <= 900.0);
@@ -531,7 +617,7 @@ fn resizing_during_a_drag_preserves_the_grab_point() {
let mut world = world();
let cursor = (world.pets[0].x + 18.0, world.pets[0].y + 30.0);
world.begin_drag(1, cursor.0, cursor.1);
world.resize_pet(1, 1000.0);
world.resize_pets(1000.0);
let pet = &world.pets[0];
assert_eq!((pet.x + 36.0, pet.y + 60.0), cursor);
world.drag_to(1, 600.0, 400.0);
@@ -545,7 +631,7 @@ fn a_resized_character_can_finish_climbing() {
for _ in 0..30 {
world.advance(0.03);
}
world.resize_pet(1, 1000.0);
world.resize_pets(1000.0);
assert!(matches!(world.pets[0].support, Some(Support::Climbing(_))));
assert_eq!(world.pets[0].x + world.pets[0].size(false, None).0, 220.0);
for _ in 0..400 {
@@ -567,7 +653,7 @@ fn a_resized_character_can_finish_climbing() {
#[test]
fn screen_fitting_preserves_the_requested_size() {
let mut world = world();
world.resize_pet(1, 1000.0);
world.resize_pets(1000.0);
let mut desktop = snapshot(Vec::new());
desktop.work_area = rectangle(40.0, 24.0, 160.0, 96.0);
world.update_desktop(desktop);
@@ -580,12 +666,63 @@ fn screen_fitting_preserves_the_requested_size() {
}
#[test]
fn each_character_has_an_independent_size() {
fn resizing_updates_existing_characters_and_new_characters_inherit_the_size() {
let mut world = world();
world.add_npc(Character::Poniko);
assert!(world.resize_pets(6.0));
assert_eq!(world.pets[0].size(false, None), (108.0, 144.0));
assert_eq!(world.pets[1].size(false, None), (108.0, 144.0));
world.add_npc(Character::Monoko);
assert_eq!(world.pets[2].size(false, None), (108.0, 144.0));
assert!(world.resize_pets(-6.0));
for pet in &world.pets {
assert_eq!(pet.size(false, None), (72.0, 96.0));
}
}
#[test]
fn resizing_multiple_characters_preserves_drag_and_titlebar_attachment() {
let mut world = world();
attach_to_target(&mut world);
world.add_npc(Character::Poniko);
let npc_id = world.pets[1].id;
world.resize_pet(1, 1000.0);
world.resize_pet(npc_id, -1000.0);
assert_eq!(world.pets[0].size(false, None), (144.0, 192.0));
assert_eq!(world.pets[1].size(false, None), (36.0, 48.0));
world.pets[1].x = 500.0;
world.pets[1].y = 500.0;
let cursor = (518.0, 530.0);
world.begin_drag(npc_id, cursor.0, cursor.1);
world.resize_pets(6.0);
let main = &world.pets[0];
let npc = &world.pets[1];
assert_eq!(main.y + main.attachment_height(false), 320.0);
assert!(matches!(&main.support, Some(Support::Window { id, .. }) if id == "target"));
assert!(npc.dragging);
assert_eq!(
(npc.x + npc.drag_offset.0, npc.y + npc.drag_offset.1),
cursor
);
assert_eq!(main.size(false, None), (108.0, 144.0));
assert_eq!(npc.size(false, None), (108.0, 144.0));
}
#[test]
fn restored_size_is_bounded_and_invalid_values_use_the_default() {
for (factor, expected_size) in [
(f64::NAN, (72.0, 96.0)),
(f64::INFINITY, (72.0, 96.0)),
(0.0, (36.0, 48.0)),
(3.0, (144.0, 192.0)),
(1.5, (108.0, 144.0)),
] {
let mut world = World::with_size_factor(factor);
world.update_desktop(snapshot(Vec::new()));
assert_eq!(world.pets[0].size(false, None), expected_size);
}
}
#[test]
fn quitting_blocks_size_changes() {
let mut world = world();
world.start_quit();
assert!(!world.resize_pets(1.0));
assert_eq!(world.pets[0].size(false, None), (72.0, 96.0));
}