Code cleanup vn
This commit is contained in:
parent
504f397a9a
commit
9b5fb1de0d
360
src/vn.rs
360
src/vn.rs
@ -2,148 +2,53 @@ use std::{collections::HashMap, io::prelude::*, net::TcpStream, path::Path};
|
|||||||
|
|
||||||
use dialogi::DialogParameter;
|
use dialogi::DialogParameter;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use maud::html;
|
use maud::{Markup, html};
|
||||||
use pukram2html::convert_subheader;
|
use pukram2html::convert_subheader;
|
||||||
use vn_settings::{
|
use vn_settings::{
|
||||||
ColorSettings, ImageSettings, LayoutSettings, Names, ObjectSettings, Parameter, PlayerSettings,
|
Change, ColorSettings, ImageSettings, LayoutSettings, Names, ObjectSettings, Parameter,
|
||||||
SettingsContext, TimingSettings,
|
PlayerSettings, SettingsContext, TimingSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::dialog::parse_map;
|
use crate::dialog::parse_map;
|
||||||
|
|
||||||
fn render_scene(settings: &PlayerSettings, name: &str, output: &mut Vec<u8>) {
|
fn render_scene(settings: &PlayerSettings, name: &str) -> Markup {
|
||||||
for object in &settings.objects.objects {
|
html! {
|
||||||
if let Some(image_set) = object.image.get(name) {
|
@for object in &settings.objects.objects {
|
||||||
for image in &settings.images.images[image_set] {
|
@if let Some(image_set) = object.image.get(name) {
|
||||||
let Some(image) = image.get_ref(name) else {
|
@for image in &settings.images.images[image_set] {
|
||||||
continue;
|
@if let Some(image_path) = image.get_ref(name) {
|
||||||
};
|
div style="position:relative; height:100%; width:100%; overflow:hidden" {
|
||||||
|
img src=(image_path)
|
||||||
let _ = write!(
|
style="height:100%; width:auto; object-fit:contain;
|
||||||
output,
|
position:absolute; left:50%; transform:translateX(-50%)";
|
||||||
r#"<div style="position:relative; height:100%; width:100%; overflow:hidden">
|
|
||||||
<img src={image:?} style="height:100%; width:auto; object-fit:contain; position:absolute; left:50%; transform:translateX(-50%)">
|
|
||||||
</div>"#,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_novel(
|
|
||||||
mut config_map: IndexMap<Box<str>, Box<str>>,
|
|
||||||
pk_path: &Path,
|
|
||||||
stream: &mut TcpStream,
|
|
||||||
start_level: usize,
|
|
||||||
) -> std::result::Result<(), dialogi::ParsingError> {
|
|
||||||
let mut player_settings = PlayerSettings {
|
|
||||||
colors: ColorSettings::common(),
|
|
||||||
timing: TimingSettings::common(),
|
|
||||||
images: ImageSettings::common(),
|
|
||||||
objects: ObjectSettings::common(),
|
|
||||||
layout: LayoutSettings::common(),
|
|
||||||
names: Names::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut layers = HashMap::new();
|
|
||||||
|
|
||||||
let keys: Vec<_> = config_map.keys().cloned().collect();
|
|
||||||
for key in keys {
|
|
||||||
let Some(("Layer", name)) = key.split_once(':') else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let value = config_map.shift_remove(&key).expect("Invalid layer");
|
|
||||||
if !value.is_empty() {
|
|
||||||
eprintln!("Layers don't accept arguments!");
|
|
||||||
}
|
}
|
||||||
layers.insert(name.into(), layers.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
if layers.is_empty() {
|
|
||||||
layers.insert("Background".into(), 0);
|
|
||||||
layers.insert("Character".into(), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut settings_context = SettingsContext {
|
|
||||||
object_cache: HashMap::new(),
|
|
||||||
layers,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (key, value) in config_map {
|
|
||||||
if let Some(parameter) = Parameter::create(&key, &mut settings_context) {
|
|
||||||
let setter = parameter.value_setter(&value, &settings_context);
|
|
||||||
player_settings.set_character_default(setter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dialogs = parse_map(pk_path, &mut settings_context)?;
|
fn navigation_controls(total_sections: usize) -> Markup {
|
||||||
|
html! {
|
||||||
let mut scenes = Vec::new();
|
div class="nav-controls" {
|
||||||
let mut sections = Vec::new();
|
button class="nav-button" onclick="prev()" { "←" }
|
||||||
|
span id="section-counter" { "1/" (total_sections) }
|
||||||
for dialog_sequence in dialogs {
|
button class="nav-button" onclick="next()" { "→" }
|
||||||
let changes = &dialog_sequence.changes;
|
|
||||||
let mut states = HashMap::new();
|
|
||||||
for parameter in changes.keys() {
|
|
||||||
states.insert(parameter.clone(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for block in &dialog_sequence.blocks {
|
|
||||||
let mut block_content = Vec::new();
|
|
||||||
|
|
||||||
for parameter in block
|
|
||||||
.lines
|
|
||||||
.iter()
|
|
||||||
.flat_map(|l| &l.actions)
|
|
||||||
.chain(&block.final_actions)
|
|
||||||
{
|
|
||||||
if let Some(state) = states.get_mut(parameter) {
|
|
||||||
let change = &dialog_sequence.changes[parameter][*state];
|
|
||||||
player_settings.change(change);
|
|
||||||
*state += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut scene_data = Vec::new();
|
fn generate_html(scenes: Vec<String>, sections: Vec<Markup>) -> Markup {
|
||||||
render_scene(&player_settings, &block.name, &mut scene_data);
|
let total_sections = sections.len();
|
||||||
scenes.push(unsafe { String::from_utf8_unchecked(scene_data) });
|
|
||||||
|
|
||||||
let section_html = if block.lines.is_empty() {
|
|
||||||
html! {}
|
|
||||||
} else {
|
|
||||||
convert_subheader(
|
|
||||||
block.lines.iter().map(|l| l.text.as_ref()),
|
|
||||||
&mut block_content,
|
|
||||||
start_level,
|
|
||||||
);
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
fieldset class="visual-novel-box" {
|
|
||||||
@if !block.name.is_empty() {
|
|
||||||
legend class="character-name" { (block.name) }
|
|
||||||
}
|
|
||||||
div class="dialog-content" {
|
|
||||||
@if let Ok(content) = String::from_utf8(block_content) {
|
|
||||||
(maud::PreEscaped(content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
sections.push(section_html);
|
|
||||||
}
|
|
||||||
|
|
||||||
player_settings.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = html! {
|
|
||||||
div id="story-container" {
|
div id="story-container" {
|
||||||
div class="textbox-container" {
|
div class="textbox-container" {
|
||||||
div class="scene-container" {
|
div class="scene-container" {
|
||||||
@for (index, scene) in scenes.iter().enumerate() {
|
@for (index, scene) in scenes.iter().enumerate() {
|
||||||
div class="scene-section" data-section-index=(index)
|
div class="scene-section"
|
||||||
|
data-section-index=(index)
|
||||||
style=(format!("display: {};", if index == 0 { "block" } else { "none" })) {
|
style=(format!("display: {};", if index == 0 { "block" } else { "none" })) {
|
||||||
div class="scene-content" {
|
div class="scene-content" {
|
||||||
(maud::PreEscaped(scene))
|
(maud::PreEscaped(scene))
|
||||||
@ -153,8 +58,9 @@ pub fn render_novel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
div class="textbox-content" {
|
div class="textbox-content" {
|
||||||
@for (index, section) in sections.iter().enumerate() {
|
@for (index, section) in sections.into_iter().enumerate() {
|
||||||
div class="story-section" data-section-index=(index)
|
div class="story-section"
|
||||||
|
data-section-index=(index)
|
||||||
style=(format!("display: {};", if index == 0 { "block" } else { "none" })) {
|
style=(format!("display: {};", if index == 0 { "block" } else { "none" })) {
|
||||||
(section)
|
(section)
|
||||||
}
|
}
|
||||||
@ -162,12 +68,15 @@ pub fn render_novel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div class="nav-controls" {
|
(navigation_controls(total_sections))
|
||||||
button class="nav-button" onclick="prev()" { "←" }
|
(global_styles())
|
||||||
span id="section-counter" { "1/" (sections.len()) }
|
(interactive_script(total_sections))
|
||||||
button class="nav-button" onclick="next()" { "→" }
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn global_styles() -> Markup {
|
||||||
|
html! {
|
||||||
style {
|
style {
|
||||||
(maud::PreEscaped(r"
|
(maud::PreEscaped(r"
|
||||||
.nav-controls {
|
.nav-controls {
|
||||||
@ -306,7 +215,7 @@ pub fn render_novel(
|
|||||||
left: 1vw;
|
left: 1vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialogue-content {
|
.dialog-content {
|
||||||
font-size: 1.6vw;
|
font-size: 1.6vw;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
max-height: 20vh;
|
max-height: 20vh;
|
||||||
@ -325,53 +234,194 @@ pub fn render_novel(
|
|||||||
}
|
}
|
||||||
"))
|
"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interactive_script(total_sections: usize) -> Markup {
|
||||||
|
html! {
|
||||||
script {
|
script {
|
||||||
(maud::PreEscaped(r"
|
(maud::PreEscaped(format!(r"
|
||||||
let currentSection = 0;
|
let currentSection = 0;
|
||||||
const totalSections = ".to_owned() + §ions.len().to_string() + r";
|
const totalSections = {total_sections};
|
||||||
|
|
||||||
function updateSection() {
|
function updateSection() {{
|
||||||
document.querySelectorAll('.story-section').forEach((el, index) => {
|
document.querySelectorAll('.story-section').forEach((el, index) => {{
|
||||||
el.style.display = index === currentSection ? 'block' : 'none';
|
el.style.display = index === currentSection ? 'block' : 'none';
|
||||||
});
|
}});
|
||||||
document.querySelectorAll('.scene-section').forEach((el, index) => {
|
document.querySelectorAll('.scene-section').forEach((el, index) => {{
|
||||||
el.style.display = index === currentSection ? 'block' : 'none';
|
el.style.display = index === currentSection ? 'block' : 'none';
|
||||||
});
|
}});
|
||||||
document.getElementById('section-counter').textContent =
|
document.getElementById('section-counter').textContent =
|
||||||
`${currentSection + 1}/${totalSections}`;
|
`${{currentSection + 1}}/${{totalSections}}`;
|
||||||
}
|
}}
|
||||||
|
|
||||||
function prev() {
|
function prev() {{
|
||||||
if (currentSection > 0) {
|
if (currentSection > 0) currentSection--;
|
||||||
--currentSection;
|
|
||||||
updateSection();
|
updateSection();
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
|
|
||||||
function next() {
|
function next() {{
|
||||||
if (currentSection < totalSections - 1) {
|
if (currentSection < totalSections - 1) currentSection++;
|
||||||
++currentSection;
|
|
||||||
updateSection();
|
updateSection();
|
||||||
|
}}
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {{
|
||||||
|
if(e.key === 'ArrowLeft') prev();
|
||||||
|
if(e.key === 'ArrowRight') next();
|
||||||
|
}});
|
||||||
|
")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
pub fn render_novel(
|
||||||
switch(e.keyCode) {
|
mut config_map: IndexMap<Box<str>, Box<str>>,
|
||||||
case 37:
|
pk_path: &Path,
|
||||||
prev();
|
stream: &mut TcpStream,
|
||||||
break;
|
start_level: usize,
|
||||||
case 39:
|
) -> Result<(), dialogi::ParsingError> {
|
||||||
next();
|
let mut player_settings = PlayerSettings {
|
||||||
break;
|
colors: ColorSettings::common(),
|
||||||
}
|
timing: TimingSettings::common(),
|
||||||
});
|
images: ImageSettings::common(),
|
||||||
"))
|
objects: ObjectSettings::common(),
|
||||||
}
|
layout: LayoutSettings::common(),
|
||||||
}
|
names: Names::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let layers = process_layers(&mut config_map);
|
||||||
|
let mut settings_context = SettingsContext {
|
||||||
|
object_cache: HashMap::new(),
|
||||||
|
layers,
|
||||||
|
};
|
||||||
|
|
||||||
|
apply_config_settings(&mut config_map, &mut settings_context, &mut player_settings);
|
||||||
|
|
||||||
|
let dialogs = parse_map(pk_path, &mut settings_context)?;
|
||||||
|
let (scenes, sections) = process_dialogs(dialogs, &mut player_settings, start_level);
|
||||||
|
|
||||||
|
let html = generate_html(scenes, sections);
|
||||||
let _ = write!(stream, "{}", html.into_string());
|
let _ = write!(stream, "{}", html.into_string());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_layers(config_map: &mut IndexMap<Box<str>, Box<str>>) -> HashMap<Box<str>, usize> {
|
||||||
|
let mut layers = HashMap::new();
|
||||||
|
|
||||||
|
let keys: Vec<_> = config_map.keys().cloned().collect();
|
||||||
|
for key in keys {
|
||||||
|
let Some(("Layer", name)) = key.split_once(':') else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = config_map.shift_remove(&key).expect("Invalid layer");
|
||||||
|
if !value.is_empty() {
|
||||||
|
eprintln!("Layers don't accept arguments!");
|
||||||
|
}
|
||||||
|
layers.insert(name.into(), layers.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
if layers.is_empty() {
|
||||||
|
layers.insert("Background".into(), 0);
|
||||||
|
layers.insert("Character".into(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
layers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_config_settings(
|
||||||
|
config_map: &mut IndexMap<Box<str>, Box<str>>,
|
||||||
|
context: &mut SettingsContext,
|
||||||
|
settings: &mut PlayerSettings,
|
||||||
|
) {
|
||||||
|
for (key, value) in config_map.drain(..) {
|
||||||
|
if let Some(parameter) = Parameter::create(&key, context) {
|
||||||
|
let setter = parameter.value_setter(&value, context);
|
||||||
|
settings.set_character_default(setter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_dialogs(
|
||||||
|
dialogs: Vec<dialogi::DialogSequence<Change, Parameter>>,
|
||||||
|
player_settings: &mut PlayerSettings,
|
||||||
|
start_level: usize,
|
||||||
|
) -> (Vec<String>, Vec<Markup>) {
|
||||||
|
let mut scenes = Vec::new();
|
||||||
|
let mut sections = Vec::new();
|
||||||
|
|
||||||
|
for dialog_sequence in dialogs {
|
||||||
|
let mut states = initialize_change_states(&dialog_sequence.changes);
|
||||||
|
|
||||||
|
for block in dialog_sequence.blocks {
|
||||||
|
apply_block_changes(
|
||||||
|
&block,
|
||||||
|
&dialog_sequence.changes,
|
||||||
|
&mut states,
|
||||||
|
player_settings,
|
||||||
|
);
|
||||||
|
|
||||||
|
scenes.push(render_scene(player_settings, &block.name).into_string());
|
||||||
|
sections.push(render_dialog_block(&block, start_level));
|
||||||
|
}
|
||||||
|
|
||||||
|
player_settings.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
(scenes, sections)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_change_states(
|
||||||
|
changes: &HashMap<Parameter, Vec<Change>>,
|
||||||
|
) -> HashMap<Parameter, usize> {
|
||||||
|
changes.keys().map(|k| (k.clone(), 0)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_block_changes(
|
||||||
|
block: &dialogi::DialogBlock<Parameter>,
|
||||||
|
changes: &HashMap<Parameter, Vec<Change>>,
|
||||||
|
states: &mut HashMap<Parameter, usize>,
|
||||||
|
settings: &mut PlayerSettings,
|
||||||
|
) {
|
||||||
|
for parameter in block
|
||||||
|
.lines
|
||||||
|
.iter()
|
||||||
|
.flat_map(|l| &l.actions)
|
||||||
|
.chain(&block.final_actions)
|
||||||
|
{
|
||||||
|
if let Some(state) = states.get_mut(parameter) {
|
||||||
|
if let Some(change) = changes[parameter].get(*state) {
|
||||||
|
settings.change(change);
|
||||||
|
*state += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_dialog_block(block: &dialogi::DialogBlock<Parameter>, start_level: usize) -> Markup {
|
||||||
|
if block.lines.is_empty() {
|
||||||
|
return html! {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content = Vec::new();
|
||||||
|
convert_subheader(
|
||||||
|
block.lines.iter().map(|l| l.text.as_ref()),
|
||||||
|
&mut content,
|
||||||
|
start_level,
|
||||||
|
);
|
||||||
|
|
||||||
|
html! {
|
||||||
|
fieldset class="visual-novel-box" {
|
||||||
|
@if !block.name.is_empty() {
|
||||||
|
legend class="character-name" { (block.name) }
|
||||||
|
}
|
||||||
|
div class="dialog-content" {
|
||||||
|
@match String::from_utf8(content) {
|
||||||
|
Ok(text) => (maud::PreEscaped(text)),
|
||||||
|
Err(e) => (maud::PreEscaped(format!("Error: {}", e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user