Added chara support
This commit is contained in:
parent
c8f2cd8b66
commit
f6bc4b2fee
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -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",
|
||||||
|
|||||||
@ -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
112
src/chara.rs
Normal 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;
|
||||||
|
}
|
||||||
|
";
|
||||||
26
src/main.rs
26
src/main.rs
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user