Added chara support

This commit is contained in:
p11 2025-05-29 21:15:51 +02:00
parent c8f2cd8b66
commit f6bc4b2fee
4 changed files with 146 additions and 0 deletions

7
Cargo.lock generated
View File

@ -17,6 +17,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]]
name = "chara"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2653cb9243a7d4453127141c162c1ba619d99c7230f1d8ad0a9cdab9cf0651ea"
[[package]] [[package]]
name = "data-stream" name = "data-stream"
version = "0.3.0" version = "0.3.0"
@ -243,6 +249,7 @@ dependencies = [
name = "pukram-server" name = "pukram-server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chara",
"data-stream", "data-stream",
"dialogi", "dialogi",
"event-simulation", "event-simulation",

View File

@ -16,3 +16,4 @@ indexmap = "2.9.0"
multilinear = "0.3.1" multilinear = "0.3.1"
multilinear-parser = "0.3.3" multilinear-parser = "0.3.3"
event-simulation = "0.1.2" event-simulation = "0.1.2"
chara = "0.2.0"

112
src/chara.rs Normal file
View File

@ -0,0 +1,112 @@
use chara::CharacterDefinition;
use maud::{Markup, html};
pub fn render_character(def: &CharacterDefinition, relative_path: &str) -> Markup {
let relative_path = relative_path.rsplit_once('/');
let relative_path = relative_path.map_or("", |(l, _r)| l);
html! {
style { (CHARACTER_CSS) }
div.customizer-container {
div.character-container {
@for layer in &def.layers {
img.character-layer
id=(format!("{}-layer", layer.internal_name))
src=(layer.entries.first().map(|e| format!("/{relative_path}/{}", e.path)).unwrap_or_default())
style=(format!("display: {};",
if !layer.entries.is_empty() && !layer.entries[0].path.is_empty() {
"block"
} else {
"none"
}
));
}
}
div.controls-column {
@for layer in &def.layers {
@if let Some(display_name) = &layer.display_name {
div.layer-group {
div.layer-title { (display_name) }
div.layer-options {
@for (i, entry) in layer.entries.iter().enumerate() {
label.option {
input type="radio"
name=(layer.internal_name)
value=(entry.path)
checked[i==0]
onchange=(format!(
"var img=document.getElementById('{}-layer');img.src=this.value;img.style.display=this.value==''?'none':'block';",
layer.internal_name
));
(entry.name)
}
}
}
}
}
}
}
}
}
}
const CHARACTER_CSS: &str = r"
.customizer-container { display: flex; gap: 20px; margin-top: 20px; }
.character-container {
position: relative;
width: 300px;
height: 400px;
border: 2px solid #ddd;
border-radius: 10px;
background-color: white;
flex-shrink: 0;
}
.character-layer {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain;
}
.controls-column {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 15px;
}
.layer-group {
background-color: white;
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.layer-title {
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.layer-options {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 8px;
}
.option {
display: flex;
align-items: center;
}
.option input {
margin-right: 8px;
}
.back-link {
display: inline-block;
margin-bottom: 15px;
color: #333;
text-decoration: none;
}
.back-link:hover {
text-decoration: underline;
}
";

View File

@ -14,6 +14,7 @@ use std::{
time::Duration, time::Duration,
}; };
use ::chara::CharacterDefinition;
use data_stream::{ use data_stream::{
FromStream, ToStream, collections::SizeSettings, default_settings::PortableSettings, FromStream, ToStream, collections::SizeSettings, default_settings::PortableSettings,
from_stream, to_stream, from_stream, to_stream,
@ -24,10 +25,12 @@ use percent_encoding::percent_decode_str;
use pukram2html::{Settings, convert, convert_extended, convert_subheader}; use pukram2html::{Settings, convert, convert_extended, convert_subheader};
use threadpool::ThreadPool; use threadpool::ThreadPool;
mod chara;
mod dialog; mod dialog;
mod request; mod request;
mod vn; mod vn;
use chara::render_character;
use request::Request; use request::Request;
use vn::render_novel; use vn::render_novel;
@ -374,6 +377,7 @@ fn handle_connection(
reply_binary(stream, &relative_path, "image", &ending, path) reply_binary(stream, &relative_path, "image", &ending, path)
} }
"mp3" | "wav" => reply_binary(stream, &relative_path, "audio", &ending, path), "mp3" | "wav" => reply_binary(stream, &relative_path, "audio", &ending, path),
"chara" => reply_chara(stream, &relative_path, path),
_ => fail(stream), _ => fail(stream),
} }
return; return;
@ -449,6 +453,28 @@ fn reply_binary(
} }
} }
fn load_character_file(path: &Path) -> Option<CharacterDefinition> {
std::fs::read_to_string(path)
.ok()
.map(|content| CharacterDefinition::parse(&content))
}
fn reply_chara(mut stream: TcpStream, relative_path: &str, mut path: PathBuf) {
path.push(relative_path);
let Some(def) = load_character_file(&path) else {
fail(stream);
return;
};
let _ = write!(stream, "HTTP/1.1 200 OK\r\n");
let _ = write!(stream, "Content-Type: text/html\r\n");
let _ = write!(stream, "\r\n");
let html = render_character(&def, relative_path);
let _ = stream.write_all(html.into_string().as_bytes());
}
fn handle_relative_connection( fn handle_relative_connection(
info: Arc<SiteInfo>, info: Arc<SiteInfo>,
mut stream: TcpStream, mut stream: TcpStream,