Initial commit

This commit is contained in:
2026-07-09 12:58:47 +08:00
commit f5a6403e72
48 changed files with 8174 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas
+5910
View File
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
[package]
name = "map-editor"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "map_editor_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
rand_xoshiro = "0.8.1"
# bounded_macro = { path = "bounded_macro" }
# Read the optimization guideline for more details: https://tauri.app/concept/size/#cargo-configuration
[profile.release]
codegen-units = 1
lto = true
opt-level = 3
panic = "abort"
strip = true
[dev-dependencies]
color-eyre = "0.6.5"
crossterm = "0.29.0"
ratatui = "0.30.2"
+46
View File
@@ -0,0 +1,46 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "bounded_macro"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
dependencies = [
"proc-macro2",
]
[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "bounded_macro"
version = "0.1.0"
edition = "2024"
[lib]
proc-macro = true
[dependencies]
quote = "1.0.46"
syn = { version = "2.0.118", features = ["full"] }
+81
View File
@@ -0,0 +1,81 @@
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn bounded_macro(attr: TokenStream, item: TokenStream) -> TokenStream {
let ast: (syn::MetaList, syn::ItemImpl) = (
syn::parse(attr).expect("Attr"),
syn::parse(item).expect("Item"),
);
impl_bounded_macro(&ast.0, &ast.1)
}
fn impl_bounded_macro(attr: &syn::MetaList, item: &syn::ItemImpl) -> TokenStream {
let picked_max = item.items.iter()
.map_while(|x| {
if let syn::ImplItem::Const(x) = x {
return Some(x);
}
None
})
.find(|x| {
x.attrs.iter().any(|x| {
x.meta.require_path_only().map_or(false, |x| {
x.get_ident().map_or(false, |x| {
x == "max"
})
})
})
}).expect("");
let picked_min = item.items.iter()
.map_while(|x| {
if let syn::ImplItem::Const(x) = x {
return Some(x);
}
None
})
.find(|x| {
x.attrs.iter().any(|x| {
x.meta.require_path_only().map_or(false, |x| {
x.get_ident().map_or(false, |x| {
x == "min"
})
})
})
}).expect("");
let syn::Expr::Lit(max) = &picked_max.expr else {
panic!()
};
let syn::Expr::Lit(min) = &picked_min.expr else {
panic!()
};
let name = match item.self_ty.as_ref() {
syn::Type::Path(p) => p,
_ => panic!()
};
quote::quote! {
impl Add<Self> for #name {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
degree: clip_range(self.degree + rhs.degree, #max, #min),
minute: self.minute + rhs.minute,
second: self.second + rhs.second,
}
}
}
impl Mul<i64> for #name {
type Output = Self;
fn mul(self, rhs: i64) -> Self {
Self {
degree: clip_range(rhs * self.degree, Self::DEGREE_MAX, Self::DEGREE_MIN),
minute: clip_range(rhs * self.minute, Self::MINUTE_MAX, Self::MINUTE_MIN),
second: clip_range(rhs * self.second, Self::SECOND_MAX, Self::SECOND_MIN),
}
}
}
}.into()
}
+3
View File
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
+10
View File
@@ -0,0 +1,10 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1020 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

+20
View File
@@ -0,0 +1,20 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
pub mod util;
pub mod map;
pub mod noise {
use rand_xoshiro::Xoshiro256StarStar as RndEngine;
}
+6
View File
@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
map_editor_lib::run()
}
+19
View File
@@ -0,0 +1,19 @@
mod coordinate;
use crate::util::BezierCurve;
use coordinate::Coordinate;
use coordinate::Latitude;
#[derive(Debug, Clone)]
pub struct BaseFactor {
seed: i64,
outline: Outline,
tropic: Latitude,
}
#[derive(Debug, Clone)]
pub struct Outline {
curve: BezierCurve<Coordinate>,
}
+58
View File
@@ -0,0 +1,58 @@
mod latitude;
mod longitude;
use std::ops::{Add, Sub, Mul, Rem};
pub use latitude::Latitude;
pub use longitude::Longitude;
#[derive(Debug, Clone, Copy)]
pub struct Coordinate {
x: Latitude,
y: Longitude,
}
impl Add for Coordinate {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl Mul<f64> for Coordinate {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
impl Mul<Coordinate> for f64 {
type Output = Coordinate;
fn mul(self, rhs: Coordinate) -> Coordinate {
rhs * self
}
}
fn clip_range<T>(x: T, upper: T, lower: T) -> T
where
T: Copy + Add<T, Output = T> + Sub<T, Output = T> + Rem<T, Output = T> + PartialOrd<T>
{
if upper < lower {
return x;
}
let len = upper - lower;
if x > lower {
(x - lower) % len + lower
} else {
len - (lower - x) % len + lower
}
}
+57
View File
@@ -0,0 +1,57 @@
use std::ops::{Add, Mul};
use super::clip_range;
#[derive(Debug, Clone, Copy)]
pub struct Latitude {
degree: i64,
minute: i64,
second: i64,
}
impl Latitude {
// const DEGREE_MAX: i64 = 90;
// const DEGREE_MIN: i64 = 0;
// const MINUTE_MAX: i64 = 60;
// const MINUTE_MIN: i64 = 0;
// const SECOND_MAX: i64 = 60;
// const SECOND_MIN: i64 = 0;
pub fn default() -> Self {
Self { degree: 0, minute: 0, second: 0 }
}
}
impl Add for Latitude {
type Output = Self;
fn add(self, rhs: Self) -> Self {
// Self {
// degree: clip_range(self.degree + rhs.degree, Self::DEGREE_MAX, Self::DEGREE_MIN),
// minute: clip_range(self.minute + rhs.minute, Self::MINUTE_MAX, Self::MINUTE_MIN),
// second: clip_range(self.second + rhs.second, Self::SECOND_MAX, Self::SECOND_MIN),
// }
todo!()
}
}
impl Mul<f64> for Latitude {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
// Self {
// degree: clip_range(rhs * self.degree, Self::DEGREE_MAX, Self::DEGREE_MIN),
// minute: clip_range(rhs * self.minute, Self::MINUTE_MAX, Self::MINUTE_MIN),
// second: clip_range(rhs * self.second, Self::SECOND_MAX, Self::SECOND_MIN),
// }
todo!()
}
}
impl Mul<Latitude> for f64 {
type Output = Latitude;
fn mul(self, rhs: Latitude) -> Latitude {
rhs * self
}
}
+57
View File
@@ -0,0 +1,57 @@
use std::ops::{Add, Mul};
use super::clip_range;
#[derive(Debug, Clone, Copy)]
pub struct Longitude {
degree: i64,
minute: i64,
second: i64,
}
impl Longitude {
// const DEGREE_MAX: i64 = 180;
// const DEGREE_MIN: i64 = -180;
// const MINUTE_MAX: i64 = 60;
// const MINUTE_MIN: i64 = 0;
// const SECOND_MAX: i64 = 60;
// const SECOND_MIN: i64 = 0;
pub fn default() -> Self {
Self { degree: 0, minute: 0, second: 0 }
}
}
impl Add for Longitude {
type Output = Self;
fn add(self, rhs: Self) -> Self {
// Self {
// degree: clip_range(self.degree + rhs.degree, Self::DEGREE_MAX, Self::DEGREE_MIN),
// minute: clip_range(self.minute + rhs.minute, Self::MINUTE_MAX, Self::MINUTE_MIN),
// second: clip_range(self.second + rhs.second, Self::SECOND_MAX, Self::SECOND_MIN),
// }
todo!()
}
}
impl Mul<f64> for Longitude {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
// Self {
// degree: clip_range(rhs * self.degree, Self::DEGREE_MAX, Self::DEGREE_MIN),
// minute: clip_range(rhs * self.minute, Self::MINUTE_MAX, Self::MINUTE_MIN),
// second: clip_range(rhs * self.second, Self::SECOND_MAX, Self::SECOND_MIN),
// }
todo!()
}
}
impl Mul<Longitude> for f64 {
type Output = Longitude;
fn mul(self, rhs: Longitude) -> Longitude {
rhs * self
}
}
+118
View File
@@ -0,0 +1,118 @@
mod basic;
use std::ops::{Add, Mul};
use basic::*;
#[derive(Debug, Clone, Default)]
pub struct BezierCurve<T>
where T: Add<T> + Mul<f64>
{
points: Vec<T>
}
impl<T> BezierCurve<T>
where T: Add<T, Output = T> + Mul<f64, Output = T>
{
pub fn new(ps: Vec<T>) -> Self {
Self { points: ps }
}
}
impl<T> BezierCurve<T>
where T: Add<T, Output = T> + Mul<f64, Output = T> + Copy
{
pub fn fetch_point(&self, mut t: f64) -> Option<T> {
if t < 0.0 {
t = 0.0;
} else if t > 1.0 {
t = 1.0;
}
if self.points.is_empty() {
return None;
}
let len: i32 = (self.points.len() - 1).try_into().ok()?;
self.points.iter().enumerate()
.map(|(i, p)| -> Option<T> {
let i: i32 = i.try_into().ok()?;
let tn: f64 = t.powi(i) * (1.0 - t).powi(len - i);
let cn: f64 = combination(len, i).try_into().ok()?;
Some(*p * tn * cn)
})
.reduce(|left, right| -> Option<T> {
left.zip(right).map(|(left, right)| left + right)
})
.flatten()
}
}
#[cfg(test)]
mod test {
use super::*;
const EPSILON: f64 = 1e-6;
#[derive(Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
impl Point {
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
impl Add for Point {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self { x: self.x + rhs.x, y: self.y + rhs.y }
}
}
impl Mul<f64> for Point {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self { x: self.x * rhs, y: self.y * rhs }
}
}
#[test]
fn curve_test() {
let test_cur = vec![
BezierCurve::new(vec![
Point::new(-50.0, -50.0),
Point::new(-50.0, 50.0),
Point::new(50.0, 50.0),
]),
];
let test_val = vec![
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
];
test_cur.iter()
.map(|cur| {
print!("Curve between ", );
cur.points.iter().for_each(|p| {
print!("{}:{}, ", p.x, p.y);
});
println!("");
test_val.iter()
.map(|x| {
cur.fetch_point(*x).inspect(|p| {
println!("t = {}, point = {:.3}:{:.3}", x, p.x, p.y);
});
})
.count();
})
.count();
}
}
+81
View File
@@ -0,0 +1,81 @@
pub fn level(mut x: i32) -> i32 {
if x <= 1 {
return 1;
}
let mut res = 1;
while x > 0 {
res = res * x;
x = x - 1;
}
res
}
pub fn arrangement(n: i32, m: i32) -> i32 {
if m < 0 || n <= 0 {
return 0;
}
if m > n {
return 0;
}
level(n) / level(n - m)
}
pub fn combination(n: i32, m: i32) -> i32 {
if m < 0 || n <= 0 {
return 0;
}
if m > n {
return 0;
}
if m == 0 {
return 1;
}
if m == n {
return 1;
}
level(n) / level(n - m) / level(m)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn util_basic_numeric() {
assert_eq!(level(0), 1);
assert_eq!(level(1), 1);
assert_eq!(level(3), 1 * 2 * 3);
assert_eq!(level(6), level(3) * 4 * 5 * 6);
assert_eq!(level(12), level(12));
assert_eq!(arrangement(0, 0), 0);
assert_eq!(arrangement(1, 0), 0);
assert_eq!(arrangement(0, 1), 0);
assert_eq!(arrangement(1, 3), 0);
assert_eq!(arrangement(5, 5), level(5));
assert_eq!(arrangement(7, 5), level(7) / level(7 - 5));
assert_eq!(combination(0, 0), 0);
assert_eq!(combination(1, 0), 0);
assert_eq!(combination(0, 1), 0);
assert_eq!(combination(1, 3), 0);
assert_eq!(combination(8, 8), 1);
assert_eq!(combination(9, 5), level(9) / level(9 - 5) / level (5));
assert_eq!(combination(8, 3), arrangement(8, 3) / arrangement(3, 3))
}
}
+36
View File
@@ -0,0 +1,36 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "map-editor",
"version": "0.1.0",
"identifier": "com.hydro.map-editor",
"build": {
"beforeDevCommand": "npm run dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "npm run build",
"frontendDist": "../dist"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "map-editor",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
+105
View File
@@ -0,0 +1,105 @@
use std::ops::{Add, Mul};
use map_editor_lib::util::BezierCurve;
use color_eyre::Result;
use crossterm::event;
use ratatui::Frame;
use ratatui::style::{Color, Stylize};
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::symbols::Marker;
use ratatui::text::{Line as TextLine, Span};
use ratatui::widgets::canvas::{Canvas, Line, Points};
#[test]
fn curve_test_full() -> Result<()> {
color_eyre::install()?;
ratatui::run(|terminal| loop {
terminal.draw(render)?;
if event::read()?.is_key_press() {
break Ok(());
}
})
}
/// Render the UI with a canvas widget.
fn render(frame: &mut Frame) {
let vertical = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).spacing(1);
let horizontal = Layout::horizontal([Constraint::Percentage(100)]).spacing(1);
let [top, main] = frame.area().layout(&vertical);
let [area] = main.layout(&horizontal);
let title = TextLine::from_iter([
Span::from("Canvas Widget").bold(),
Span::from(" (Press 'q' to quit)"),
]);
frame.render_widget(title.centered(), top);
render_canvas(frame, area);
}
/// Renders the canvas widget with various shapes and a map.
pub fn render_canvas(frame: &mut Frame, area: Rect) {
let sample_t = vec![
0.0,
0.2,
0.4,
0.6,
0.8,
1.0,
];
let curve = BezierCurve::new(vec![
Point::new(-50.0, -50.0),
Point::new(-50.0, 50.0),
Point::new(50.0, 50.0),
]);
let canvas = Canvas::default()
.x_bounds([-180.0, 180.0])
.y_bounds([-90.0, 90.0])
.marker(Marker::Braille)
.paint(|ctx| {
ctx.draw(&Points::new(&[
(-50.0, -50.0),
(-50.0, 50.0),
(50.0, 50.0),
], Color::Black));
// sample_t.windows(2)
// .map_while(|a| {
// curve.fetch_point(a[0]).zip(curve.fetch_point(a[1]))
// })
// .for_each(|(x, y)| {
// ctx.draw(&Line::new(x.x, x.y, y.x, y.y, Color::Blue));
// });
});
frame.render_widget(canvas, area);
}
const EPSILON: f64 = 1e-6;
#[derive(Clone, Copy)]
struct Point {
x: f64,
y: f64,
}
impl Point {
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
impl Add for Point {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self { x: self.x + rhs.x, y: self.y + rhs.y }
}
}
impl Mul<f64> for Point {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self { x: self.x * rhs, y: self.y * rhs }
}
}