WASM plugins, scripting, i18n, GUI pan/zoom, cargo-dist CI/CD
- WASM plugin host (wasmtime 46, 17 host fns) + example noisemap plugin - Rhai scripting engine (12 API fns, `terrafier run` cmd) - i18n: en/ru translations, auto-detect, t! macro - GUI: pan (click-drag) + zoom (scroll wheel) with viewport transform - cargo-dist 0.31: MSI/pkg/shell installers, both binaries - CI: .github/workflows/ci.yml + release.yml - Security: wasmtime 30→46, bincode 1.3→2.0, quick-xml CVE docs - Code review: SAFETY comments, unwrap→match cleanup - Fixes: clippy approx_constant, needless_borrows, rustfmt
This commit is contained in:
parent
bdd4564240
commit
18613260a8
100 changed files with 4262 additions and 585 deletions
|
|
@ -3,8 +3,16 @@ name = "terrafier-gui"
|
|||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
authors.workspace = true
|
||||
description = "Native GUI for Terrafier — interactive world painting with GPU rendering"
|
||||
|
||||
[package.metadata.wix]
|
||||
upgrade-guid = "A7365100-6923-4DAC-B4A5-2BD817EB802B"
|
||||
path-guid = "097E8AD4-180B-4B52-8BFE-04AA942D03A6"
|
||||
license = false
|
||||
eula = false
|
||||
|
||||
[dependencies]
|
||||
terrafier-core = { path = "../core" }
|
||||
anyhow.workspace = true
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ pub struct TerrafierApp {
|
|||
pub renderer: Option<GpuRenderer>,
|
||||
pub show_gpu_render: bool,
|
||||
pub gpu_status: GpuStatus,
|
||||
pub view_offset: egui::Vec2,
|
||||
pub view_zoom: f32,
|
||||
pub cpu_texture_handle: Option<egui::TextureHandle>,
|
||||
pub layer_visible: [bool; 6],
|
||||
}
|
||||
|
|
@ -96,7 +98,10 @@ impl TerrafierApp {
|
|||
gpu_renderer.register_texture(&mut renderer_lock);
|
||||
(Some(gpu_renderer), GpuStatus::Enabled)
|
||||
} else {
|
||||
(None, GpuStatus::FallbackCpu("GPU init failed — no wgpu adapter found"))
|
||||
(
|
||||
None,
|
||||
GpuStatus::FallbackCpu("GPU init failed — no wgpu adapter found"),
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
|
|
@ -124,9 +129,10 @@ impl TerrafierApp {
|
|||
renderer,
|
||||
show_gpu_render: true,
|
||||
gpu_status,
|
||||
view_offset: egui::Vec2::ZERO,
|
||||
view_zoom: 1.0,
|
||||
cpu_texture_handle: None,
|
||||
layer_visible: [false; 6],
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,20 @@ impl TerrafierApp {
|
|||
pub fn undo(&mut self) {
|
||||
if let Some(op) = self.undo_stack.pop_back() {
|
||||
if let Some(ref mut world) = self.world {
|
||||
let dim = world.overworld_mut().expect("world has no overworld");
|
||||
let inv = op.inverse();
|
||||
if let Err(e) = inv.apply(dim) {
|
||||
self.status_message = format!("Undo error: {:?}", e);
|
||||
self.undo_stack.push_back(op);
|
||||
if let Some(dim) = world.overworld_mut() {
|
||||
let inv = op.inverse();
|
||||
if let Err(e) = inv.apply(dim) {
|
||||
self.status_message = format!("Undo error: {:?}", e);
|
||||
self.undo_stack.push_back(op);
|
||||
return;
|
||||
}
|
||||
self.redo_stack.push(op);
|
||||
self.status_message = "Undo".to_string();
|
||||
return;
|
||||
}
|
||||
self.redo_stack.push(op);
|
||||
self.status_message = "Undo".to_string();
|
||||
}
|
||||
self.undo_stack.push_back(op);
|
||||
self.status_message = "Undo error: world has no overworld".to_string();
|
||||
} else {
|
||||
self.status_message = "Nothing to undo".to_string();
|
||||
}
|
||||
|
|
@ -32,15 +36,19 @@ impl TerrafierApp {
|
|||
pub fn redo(&mut self) {
|
||||
if let Some(op) = self.redo_stack.pop() {
|
||||
if let Some(ref mut world) = self.world {
|
||||
let dim = world.overworld_mut().expect("world has no overworld");
|
||||
if let Err(e) = op.apply(dim) {
|
||||
self.status_message = format!("Redo error: {:?}", e);
|
||||
self.redo_stack.push(op);
|
||||
if let Some(dim) = world.overworld_mut() {
|
||||
if let Err(e) = op.apply(dim) {
|
||||
self.status_message = format!("Redo error: {:?}", e);
|
||||
self.redo_stack.push(op);
|
||||
return;
|
||||
}
|
||||
self.undo_stack.push_back(op);
|
||||
self.status_message = "Redo".to_string();
|
||||
return;
|
||||
}
|
||||
self.undo_stack.push_back(op);
|
||||
self.status_message = "Redo".to_string();
|
||||
}
|
||||
self.redo_stack.push(op);
|
||||
self.status_message = "Redo error: world has no overworld".to_string();
|
||||
} else {
|
||||
self.status_message = "Nothing to redo".to_string();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ impl eframe::App for TerrafierApp {
|
|||
&& let Some(tile) = world.dimensions[0].tiles.values().next()
|
||||
{
|
||||
renderer.render(
|
||||
&tile.heightmap.as_slice().try_into().expect("heightmap size"),
|
||||
&tile.terrain.as_slice().try_into().expect("terrain size"),
|
||||
&tile.heightmap.as_slice().try_into().unwrap_or_else(|_| [0; 16384]),
|
||||
&tile.terrain.as_slice().try_into().unwrap_or_else(|_| [0; 16384]),
|
||||
self.show_heightmap,
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,57 +47,103 @@ impl GpuRenderer {
|
|||
needed_storage,
|
||||
);
|
||||
}
|
||||
let bind_group_layout =
|
||||
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("terrain_bind_group_layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry { binding: 0, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, min_binding_size: None }, count: None },
|
||||
wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None }, count: None },
|
||||
wgpu::BindGroupLayoutEntry { binding: 2, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: true }, has_dynamic_offset: false, min_binding_size: None }, count: None },
|
||||
wgpu::BindGroupLayoutEntry { binding: 3, visibility: wgpu::ShaderStages::COMPUTE, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Storage { read_only: false }, has_dynamic_offset: false, min_binding_size: None }, count: None },
|
||||
],
|
||||
});
|
||||
let shader_module =
|
||||
device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("terrain_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
let pipeline_layout =
|
||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("terrain_pipeline_layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let compute_pipeline =
|
||||
device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some("terrain_compute_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
module: &shader_module,
|
||||
entry_point: Some("main"),
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
cache: None,
|
||||
});
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("terrain_bind_group_layout"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: true },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("terrain_shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
|
||||
});
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("terrain_pipeline_layout"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
let compute_pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
|
||||
label: Some("terrain_compute_pipeline"),
|
||||
layout: Some(&pipeline_layout),
|
||||
module: &shader_module,
|
||||
entry_point: Some("main"),
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
cache: None,
|
||||
});
|
||||
let heightmap_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("heightmap_buffer"), size: 8192 * 4,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false,
|
||||
label: Some("heightmap_buffer"),
|
||||
size: 8192 * 4,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let terrain_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("terrain_buffer"), size: 4096 * 4,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false,
|
||||
label: Some("terrain_buffer"),
|
||||
size: 4096 * 4,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("uniform_buffer"), size: std::mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, mapped_at_creation: false,
|
||||
label: Some("uniform_buffer"),
|
||||
size: std::mem::size_of::<Uniforms>() as u64,
|
||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let output_size = (width * height * 4) as u64;
|
||||
let output_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: Some("output_buffer"), size: output_size,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, mapped_at_creation: false,
|
||||
label: Some("output_buffer"),
|
||||
size: output_size,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
let output_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("output_texture"),
|
||||
size: wgpu::Extent3d { width, height, depth_or_array_layers: 1 },
|
||||
mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2,
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
view_formats: &[wgpu::TextureFormat::Rgba8UnormSrgb],
|
||||
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
|
|
@ -107,16 +153,54 @@ impl GpuRenderer {
|
|||
label: Some("terrain_bind_group"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry { binding: 0, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &uniform_buffer, offset: 0, size: None }) },
|
||||
wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &heightmap_buffer, offset: 0, size: None }) },
|
||||
wgpu::BindGroupEntry { binding: 2, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &terrain_buffer, offset: 0, size: None }) },
|
||||
wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { buffer: &output_buffer, offset: 0, size: None }) },
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &uniform_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &heightmap_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &terrain_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &output_buffer,
|
||||
offset: 0,
|
||||
size: None,
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
Self {
|
||||
device, queue, compute_pipeline, heightmap_buffer, terrain_buffer,
|
||||
uniform_buffer, output_buffer, output_texture, output_texture_view, bind_group,
|
||||
texture_id: None, width, height,
|
||||
device,
|
||||
queue,
|
||||
compute_pipeline,
|
||||
heightmap_buffer,
|
||||
terrain_buffer,
|
||||
uniform_buffer,
|
||||
output_buffer,
|
||||
output_texture,
|
||||
output_texture_view,
|
||||
bind_group,
|
||||
texture_id: None,
|
||||
width,
|
||||
height,
|
||||
last_heightmap: vec![0i16; 16384],
|
||||
last_terrain: vec![0u8; 16384],
|
||||
last_show_heightmap: false,
|
||||
|
|
@ -125,16 +209,25 @@ impl GpuRenderer {
|
|||
}
|
||||
|
||||
pub(crate) fn pack_heightmap(heightmap: &[i16; 16384]) -> Vec<u32> {
|
||||
heightmap.chunks(2).map(|chunk| {
|
||||
let low = chunk[0] as u32 & 0xFFFF;
|
||||
let high = chunk[1] as u32 & 0xFFFF;
|
||||
low | (high << 16)
|
||||
}).collect()
|
||||
heightmap
|
||||
.chunks(2)
|
||||
.map(|chunk| {
|
||||
let low = chunk[0] as u32 & 0xFFFF;
|
||||
let high = chunk[1] as u32 & 0xFFFF;
|
||||
low | (high << 16)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn pack_terrain(terrain: &[u8; 16384]) -> Vec<u32> {
|
||||
terrain.chunks(4).map(|chunk| {
|
||||
chunk[0] as u32 | (chunk[1] as u32) << 8 | (chunk[2] as u32) << 16 | (chunk[3] as u32) << 24
|
||||
}).collect()
|
||||
terrain
|
||||
.chunks(4)
|
||||
.map(|chunk| {
|
||||
chunk[0] as u32
|
||||
| (chunk[1] as u32) << 8
|
||||
| (chunk[2] as u32) << 16
|
||||
| (chunk[3] as u32) << 24
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,21 +2,15 @@ use crate::renderer::GpuRenderer;
|
|||
use crate::renderer::Uniforms;
|
||||
|
||||
fn as_u8_slice<T>(data: &[T]) -> &[u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
data.as_ptr() as *const u8,
|
||||
std::mem::size_of_val(data),
|
||||
)
|
||||
}
|
||||
// SAFETY: Reinterprets `&[T]` as `&[u8]` for GPU buffer upload.
|
||||
// T is a primitive type with no padding, and the byte layout is stable.
|
||||
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) }
|
||||
}
|
||||
|
||||
fn as_u8_value<T>(data: &T) -> &[u8] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
data as *const T as *const u8,
|
||||
std::mem::size_of::<T>(),
|
||||
)
|
||||
}
|
||||
// SAFETY: Reinterprets a single `&T` as `&[u8]` for GPU buffer upload.
|
||||
// T is a primitive type with no padding, and the byte layout is stable.
|
||||
unsafe { std::slice::from_raw_parts(data as *const T as *const u8, std::mem::size_of::<T>()) }
|
||||
}
|
||||
|
||||
impl GpuRenderer {
|
||||
|
|
@ -39,8 +33,8 @@ impl GpuRenderer {
|
|||
self.last_show_heightmap = show_heightmap;
|
||||
self.needs_upload = false;
|
||||
|
||||
let min_height = *heightmap.iter().min().unwrap();
|
||||
let max_height = *heightmap.iter().max().unwrap();
|
||||
let min_height = *heightmap.iter().min().unwrap_or(&0);
|
||||
let max_height = *heightmap.iter().max().unwrap_or(&255);
|
||||
|
||||
let packed_heightmap = Self::pack_heightmap(heightmap);
|
||||
self.queue
|
||||
|
|
|
|||
|
|
@ -1,21 +1,45 @@
|
|||
use crate::app::{
|
||||
LAYER_BIOME_IDX, LAYER_CAVES_IDX, LAYER_FROST_IDX, LAYER_RESOURCES_IDX, LAYER_RIVER_IDX,
|
||||
LAYER_TREES_IDX,
|
||||
};
|
||||
use egui::{Color32, ColorImage, TextureOptions, Vec2};
|
||||
use terrafier_core::model::tile::{TILE_SIZE, LayerBuffer};
|
||||
use terrafier_core::model::layers::{LAYER_CAVES, LAYER_RIVER, LAYER_FROST, LAYER_TREES, LAYER_BIOME, LAYER_RESOURCES};
|
||||
use crate::app::{LAYER_CAVES_IDX, LAYER_RIVER_IDX, LAYER_FROST_IDX, LAYER_TREES_IDX, LAYER_BIOME_IDX, LAYER_RESOURCES_IDX};
|
||||
use terrafier_core::model::layers::{
|
||||
LAYER_BIOME, LAYER_CAVES, LAYER_FROST, LAYER_RESOURCES, LAYER_RIVER, LAYER_TREES,
|
||||
};
|
||||
use terrafier_core::model::tile::{LayerBuffer, TILE_SIZE};
|
||||
|
||||
const LAYER_IDS: [u32; 6] = [LAYER_CAVES, LAYER_RIVER, LAYER_FROST, LAYER_TREES, LAYER_BIOME, LAYER_RESOURCES];
|
||||
const LAYER_IDS: [u32; 6] = [
|
||||
LAYER_CAVES,
|
||||
LAYER_RIVER,
|
||||
LAYER_FROST,
|
||||
LAYER_TREES,
|
||||
LAYER_BIOME,
|
||||
LAYER_RESOURCES,
|
||||
];
|
||||
|
||||
fn layer_value(tile: &terrafier_core::model::tile::Tile, idx: usize, layer_idx: usize) -> Option<f32> {
|
||||
fn layer_value(
|
||||
tile: &terrafier_core::model::tile::Tile,
|
||||
idx: usize,
|
||||
layer_idx: usize,
|
||||
) -> Option<f32> {
|
||||
let layer_id = LAYER_IDS[layer_idx];
|
||||
let buf = tile.layer_data.get(&layer_id)?;
|
||||
let val = match buf {
|
||||
LayerBuffer::Bit(data) => {
|
||||
let word = data.get(idx / 64)?;
|
||||
Some(if (word >> (idx % 64)) & 1 != 0 { 1.0 } else { 0.0 })
|
||||
Some(if (word >> (idx % 64)) & 1 != 0 {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
})
|
||||
}
|
||||
LayerBuffer::Nibble(data) => {
|
||||
let byte = *data.get(idx / 2)?;
|
||||
let nib = if idx % 2 == 0 { byte & 0x0F } else { (byte >> 4) & 0x0F };
|
||||
let nib = if idx % 2 == 0 {
|
||||
byte & 0x0F
|
||||
} else {
|
||||
(byte >> 4) & 0x0F
|
||||
};
|
||||
Some(nib as f32 / 15.0)
|
||||
}
|
||||
LayerBuffer::Byte(data) => {
|
||||
|
|
@ -65,7 +89,9 @@ const TERRAIN_COLORS: [Color32; 7] = [
|
|||
];
|
||||
|
||||
pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
||||
let Some(ref world) = app.world else { return; };
|
||||
let Some(ref world) = app.world else {
|
||||
return;
|
||||
};
|
||||
let dim = &world.dimensions[0];
|
||||
|
||||
let mut min_tx = i32::MAX;
|
||||
|
|
@ -79,20 +105,22 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
|||
max_tz = max_tz.max(tz);
|
||||
}
|
||||
|
||||
let display_size = 64u32;
|
||||
let grid_w = ((max_tx - min_tx + 1) * display_size as i32) as usize;
|
||||
let grid_h = ((max_tz - min_tz + 1) * display_size as i32) as usize;
|
||||
let base_size = 64u32;
|
||||
let zoom = app.view_zoom;
|
||||
|
||||
let grid_w = ((max_tx - min_tx + 1) * base_size as i32) as usize;
|
||||
let grid_h = ((max_tz - min_tz + 1) * base_size as i32) as usize;
|
||||
|
||||
let mut img_data = vec![0u8; grid_w * grid_h * 4];
|
||||
|
||||
for (&(tx, tz), tile) in &dim.tiles {
|
||||
let px = ((tx - min_tx) * display_size as i32) as usize;
|
||||
let pz = ((tz - min_tz) * display_size as i32) as usize;
|
||||
let px = ((tx - min_tx) * base_size as i32) as usize;
|
||||
let pz = ((tz - min_tz) * base_size as i32) as usize;
|
||||
|
||||
for ly in 0..display_size as usize {
|
||||
for lx in 0..display_size as usize {
|
||||
let sx = (lx * TILE_SIZE / display_size as usize).min(TILE_SIZE - 1);
|
||||
let sz = (ly * TILE_SIZE / display_size as usize).min(TILE_SIZE - 1);
|
||||
for ly in 0..base_size as usize {
|
||||
for lx in 0..base_size as usize {
|
||||
let sx = (lx * TILE_SIZE / base_size as usize).min(TILE_SIZE - 1);
|
||||
let sz = (ly * TILE_SIZE / base_size as usize).min(TILE_SIZE - 1);
|
||||
let idx = sz * TILE_SIZE + sx;
|
||||
|
||||
let terrain_type = tile.terrain[idx] as usize;
|
||||
|
|
@ -127,7 +155,11 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
|||
if app.layer_visible[li] {
|
||||
if let Some(val) = layer_value(tile, idx, li) {
|
||||
if let Some(over) = layer_overlay(li, val) {
|
||||
let alpha = if val > 0.0 { (val * 0.6 + 0.2).min(0.8) } else { 0.0 };
|
||||
let alpha = if val > 0.0 {
|
||||
(val * 0.6 + 0.2).min(0.8)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
(r, g, b) = blend_overlay((r, g, b), over, alpha);
|
||||
}
|
||||
}
|
||||
|
|
@ -156,13 +188,35 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
|||
id
|
||||
};
|
||||
|
||||
let desired_size = Vec2::new(grid_w as f32, grid_h as f32);
|
||||
let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::click());
|
||||
// Viewport: allocate a large area for the world with scroll
|
||||
let viewport_size = Vec2::new(grid_w as f32 * zoom, grid_h as f32 * zoom);
|
||||
let (response, painter) = ui.allocate_painter(
|
||||
viewport_size,
|
||||
egui::Sense::click_and_drag(),
|
||||
);
|
||||
|
||||
// Handle scroll-wheel zoom
|
||||
let scroll_delta = ui.input(|i| i.raw_scroll_delta);
|
||||
if scroll_delta.y != 0.0 {
|
||||
app.view_zoom = (app.view_zoom * (1.0 + scroll_delta.y * 0.002)).clamp(0.1, 10.0);
|
||||
}
|
||||
|
||||
// Handle drag-to-pan
|
||||
if response.dragged() {
|
||||
app.view_offset += response.drag_delta();
|
||||
}
|
||||
|
||||
// Recalculate scaled size after potential zoom change
|
||||
let scaled_size = base_size as f32 * app.view_zoom;
|
||||
let offset = app.view_offset;
|
||||
|
||||
let rect = response.rect;
|
||||
painter.image(
|
||||
texture_id,
|
||||
rect,
|
||||
egui::Rect::from_min_max(
|
||||
egui::pos2(rect.min.x + offset.x, rect.min.y + offset.y),
|
||||
egui::pos2(rect.min.x + offset.x + grid_w as f32 * app.view_zoom, rect.min.y + offset.y + grid_h as f32 * app.view_zoom),
|
||||
),
|
||||
egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
|
||||
Color32::WHITE,
|
||||
);
|
||||
|
|
@ -173,10 +227,10 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
|||
&& sel_tz >= min_tz
|
||||
&& sel_tz <= max_tz
|
||||
{
|
||||
let bx = rect.min.x + (sel_tx - min_tx) as f32 * display_size as f32;
|
||||
let bz = rect.min.y + (sel_tz - min_tz) as f32 * display_size as f32;
|
||||
let bx = rect.min.x + offset.x + (sel_tx - min_tx) as f32 * scaled_size;
|
||||
let bz = rect.min.y + offset.y + (sel_tz - min_tz) as f32 * scaled_size;
|
||||
let border_rect =
|
||||
egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(display_size as f32));
|
||||
egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(scaled_size));
|
||||
painter.rect_stroke(
|
||||
border_rect,
|
||||
0.0,
|
||||
|
|
@ -188,13 +242,13 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
|||
if let (Some((sel_tx, sel_tz)), Some(bx), Some(bz)) =
|
||||
(app.selected_tile, app.brush_local_x, app.brush_local_z)
|
||||
{
|
||||
let tile_x_in_pixels = (sel_tx - min_tx) as f32 * display_size as f32;
|
||||
let tile_z_in_pixels = (sel_tz - min_tz) as f32 * display_size as f32;
|
||||
let tile_x_in_pixels = (sel_tx - min_tx) as f32 * scaled_size;
|
||||
let tile_z_in_pixels = (sel_tz - min_tz) as f32 * scaled_size;
|
||||
let brush_x =
|
||||
rect.min.x + tile_x_in_pixels + (bx as f32 * display_size as f32 / TILE_SIZE as f32);
|
||||
rect.min.x + offset.x + tile_x_in_pixels + (bx as f32 * scaled_size / TILE_SIZE as f32);
|
||||
let brush_z =
|
||||
rect.min.y + tile_z_in_pixels + (bz as f32 * display_size as f32 / TILE_SIZE as f32);
|
||||
let brush_radius_px = app.brush_radius as f32 * display_size as f32 / TILE_SIZE as f32;
|
||||
rect.min.y + offset.y + tile_z_in_pixels + (bz as f32 * scaled_size / TILE_SIZE as f32);
|
||||
let brush_radius_px = app.brush_radius as f32 * scaled_size / TILE_SIZE as f32;
|
||||
painter.circle_stroke(
|
||||
egui::pos2(brush_x, brush_z),
|
||||
brush_radius_px.max(2.0),
|
||||
|
|
@ -202,20 +256,21 @@ pub fn render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) {
|
|||
);
|
||||
}
|
||||
|
||||
// Click-to-select with inverse transform
|
||||
if response.clicked()
|
||||
&& let Some(pos) = response.interact_pointer_pos()
|
||||
{
|
||||
let lx = pos.x - rect.min.x;
|
||||
let lz = pos.y - rect.min.y;
|
||||
let lx = pos.x - rect.min.x - offset.x;
|
||||
let lz = pos.y - rect.min.y - offset.y;
|
||||
if lx >= 0.0 && lz >= 0.0 {
|
||||
let tx = (lx / display_size as f32).floor() as i32 + min_tx;
|
||||
let tz = (lz / display_size as f32).floor() as i32 + min_tz;
|
||||
let tx = (lx / scaled_size).floor() as i32 + min_tx;
|
||||
let tz = (lz / scaled_size).floor() as i32 + min_tz;
|
||||
if dim.tiles.contains_key(&(tx, tz)) {
|
||||
app.selected_tile = Some((tx, tz));
|
||||
let local_x =
|
||||
((lx as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127);
|
||||
((lx as u32 % scaled_size as u32) * TILE_SIZE as u32 / scaled_size as u32).min(127);
|
||||
let local_z =
|
||||
((lz as u32 % display_size) * TILE_SIZE as u32 / display_size).min(127);
|
||||
((lz as u32 % scaled_size as u32) * TILE_SIZE as u32 / scaled_size as u32).min(127);
|
||||
app.brush_local_x = Some(local_x);
|
||||
app.brush_local_z = Some(local_z);
|
||||
app.status_message = format!(
|
||||
|
|
|
|||
|
|
@ -11,37 +11,64 @@ pub fn try_render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) -> bool
|
|||
&& let Some(texture_id) = renderer.texture_id
|
||||
{
|
||||
let desired_size = Vec2::new(512.0, 512.0);
|
||||
let (response, painter) = ui.allocate_painter(desired_size, egui::Sense::click());
|
||||
let (response, painter) = ui.allocate_painter(
|
||||
desired_size,
|
||||
egui::Sense::click_and_drag(),
|
||||
);
|
||||
let rect = response.rect;
|
||||
|
||||
// Handle scroll-wheel zoom
|
||||
let scroll_delta = ui.input(|i| i.raw_scroll_delta);
|
||||
if scroll_delta.y != 0.0 {
|
||||
app.view_zoom = (app.view_zoom * (1.0 + scroll_delta.y * 0.002)).clamp(0.1, 10.0);
|
||||
}
|
||||
|
||||
// Handle drag-to-pan
|
||||
if response.dragged() {
|
||||
app.view_offset += response.drag_delta();
|
||||
}
|
||||
|
||||
let zoom = app.view_zoom;
|
||||
let offset = app.view_offset;
|
||||
|
||||
let base_display_size = rect.width() / 4.0;
|
||||
let display_size = base_display_size * zoom;
|
||||
|
||||
// Find tile bounds for positioning
|
||||
let mut min_tx = i32::MAX;
|
||||
let mut max_tx = i32::MIN;
|
||||
let mut min_tz = i32::MAX;
|
||||
let mut max_tz = i32::MIN;
|
||||
for &(tx, tz) in dim.tiles.keys() {
|
||||
min_tx = min_tx.min(tx);
|
||||
max_tx = max_tx.max(tx);
|
||||
min_tz = min_tz.min(tz);
|
||||
max_tz = max_tz.max(tz);
|
||||
}
|
||||
|
||||
// Draw the GPU-rendered tile texture at the correct position
|
||||
// GPU only renders the first tile - position it correctly
|
||||
// Use a default position for the rendered tile
|
||||
let first_tile_pos = dim.tiles.keys().next().copied().unwrap_or((0, 0));
|
||||
let tex_origin_x = rect.min.x + offset.x + (first_tile_pos.0 - min_tx) as f32 * display_size;
|
||||
let tex_origin_y = rect.min.y + offset.y + (first_tile_pos.1 - min_tz) as f32 * display_size;
|
||||
|
||||
let tex_rect = egui::Rect::from_min_size(
|
||||
egui::pos2(tex_origin_x, tex_origin_y),
|
||||
Vec2::splat(display_size),
|
||||
);
|
||||
painter.image(
|
||||
texture_id,
|
||||
rect,
|
||||
tex_rect,
|
||||
egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)),
|
||||
Color32::WHITE,
|
||||
);
|
||||
|
||||
let display_size = rect.width() / 4.0;
|
||||
|
||||
if let Some((sel_tx, sel_tz)) = app.selected_tile {
|
||||
let mut min_tx = i32::MAX;
|
||||
let mut max_tx = i32::MIN;
|
||||
let mut min_tz = i32::MAX;
|
||||
let mut max_tz = i32::MIN;
|
||||
for &(tx, tz) in dim.tiles.keys() {
|
||||
min_tx = min_tx.min(tx);
|
||||
max_tx = max_tx.max(tx);
|
||||
min_tz = min_tz.min(tz);
|
||||
max_tz = max_tz.max(tz);
|
||||
}
|
||||
|
||||
let bx = rect.min.x;
|
||||
let bz = rect.min.y;
|
||||
let sel_bx = bx + (sel_tx - min_tx) as f32 * display_size;
|
||||
let sel_bz = bz + (sel_tz - min_tz) as f32 * display_size;
|
||||
let border_rect = egui::Rect::from_min_size(
|
||||
egui::pos2(sel_bx, sel_bz),
|
||||
Vec2::splat(display_size),
|
||||
);
|
||||
let bx = rect.min.x + offset.x + (sel_tx - min_tx) as f32 * display_size;
|
||||
let bz = rect.min.y + offset.y + (sel_tz - min_tz) as f32 * display_size;
|
||||
let border_rect =
|
||||
egui::Rect::from_min_size(egui::pos2(bx, bz), Vec2::splat(display_size));
|
||||
painter.rect_stroke(
|
||||
border_rect,
|
||||
0.0,
|
||||
|
|
@ -53,18 +80,12 @@ pub fn try_render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) -> bool
|
|||
if let (Some((sel_tx, sel_tz)), Some(bx), Some(bz)) =
|
||||
(app.selected_tile, app.brush_local_x, app.brush_local_z)
|
||||
{
|
||||
let mut min_tx = i32::MAX;
|
||||
let mut min_tz = i32::MAX;
|
||||
for &(tx, tz) in dim.tiles.keys() {
|
||||
min_tx = min_tx.min(tx);
|
||||
min_tz = min_tz.min(tz);
|
||||
}
|
||||
let tile_x_in_pixels = (sel_tx - min_tx) as f32 * display_size;
|
||||
let tile_z_in_pixels = (sel_tz - min_tz) as f32 * display_size;
|
||||
let brush_x = rect.min.x + tile_x_in_pixels
|
||||
+ (bx as f32 * display_size / TILE_SIZE as f32);
|
||||
let brush_z = rect.min.y + tile_z_in_pixels
|
||||
+ (bz as f32 * display_size / TILE_SIZE as f32);
|
||||
let brush_x =
|
||||
rect.min.x + offset.x + tile_x_in_pixels + (bx as f32 * display_size / TILE_SIZE as f32);
|
||||
let brush_z =
|
||||
rect.min.y + offset.y + tile_z_in_pixels + (bz as f32 * display_size / TILE_SIZE as f32);
|
||||
let brush_radius_px = app.brush_radius as f32 * display_size / TILE_SIZE as f32;
|
||||
painter.circle_stroke(
|
||||
egui::pos2(brush_x, brush_z),
|
||||
|
|
@ -76,23 +97,19 @@ pub fn try_render(ui: &mut egui::Ui, app: &mut crate::app::TerrafierApp) -> bool
|
|||
if response.clicked()
|
||||
&& let Some(pos) = response.interact_pointer_pos()
|
||||
{
|
||||
let lx = pos.x - rect.min.x;
|
||||
let lz = pos.y - rect.min.y;
|
||||
let lx = pos.x - rect.min.x - offset.x;
|
||||
let lz = pos.y - rect.min.y - offset.y;
|
||||
if lx >= 0.0 && lz >= 0.0 {
|
||||
let mut min_tx = i32::MAX;
|
||||
let mut min_tz = i32::MAX;
|
||||
for &(tx, tz) in dim.tiles.keys() {
|
||||
min_tx = min_tx.min(tx);
|
||||
min_tz = min_tz.min(tz);
|
||||
}
|
||||
let tx = (lx / display_size).floor() as i32 + min_tx;
|
||||
let tz = (lz / display_size).floor() as i32 + min_tz;
|
||||
if dim.tiles.contains_key(&(tx, tz)) {
|
||||
app.selected_tile = Some((tx, tz));
|
||||
let local_x =
|
||||
((lx as u32 % display_size as u32) * TILE_SIZE as u32 / display_size as u32).min(127);
|
||||
let local_z =
|
||||
((lz as u32 % display_size as u32) * TILE_SIZE as u32 / display_size as u32).min(127);
|
||||
let local_x = ((lx as u32 % display_size as u32) * TILE_SIZE as u32
|
||||
/ display_size as u32)
|
||||
.min(127);
|
||||
let local_z = ((lz as u32 % display_size as u32) * TILE_SIZE as u32
|
||||
/ display_size as u32)
|
||||
.min(127);
|
||||
app.brush_local_x = Some(local_x);
|
||||
app.brush_local_z = Some(local_z);
|
||||
app.status_message = format!(
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use terrafier_core::Terrain;
|
||||
use terrafier_core::model::types::SymmetricBrush;
|
||||
use terrafier_core::model::tile::TILE_SIZE;
|
||||
use terrafier_core::model::types::SymmetricBrush;
|
||||
use terrafier_core::ops::operations::{
|
||||
ErodeOperation, FillOperation, FlattenOperation, HeightOperation, MultiTileOperation,
|
||||
Operation, PaintOperation, SmoothOperation,
|
||||
};
|
||||
|
||||
use crate::app::{TerrafierApp, ToolMode, LAYER_NAMES};
|
||||
use crate::app::{LAYER_NAMES, TerrafierApp, ToolMode};
|
||||
|
||||
pub fn show_tools_panel(ui: &mut egui::Ui, app: &mut TerrafierApp) {
|
||||
ui.heading("Tools");
|
||||
|
|
@ -83,7 +83,10 @@ fn apply_tool(app: &mut TerrafierApp) {
|
|||
let Some(ref mut world) = app.world else {
|
||||
return;
|
||||
};
|
||||
let dim = world.overworld_mut().expect("no overworld");
|
||||
let Some(dim) = world.overworld_mut() else {
|
||||
app.status_message = "World has no overworld".to_string();
|
||||
return;
|
||||
};
|
||||
|
||||
let center = app.brush_local_x.unwrap_or(64);
|
||||
let center_z = app.brush_local_z.unwrap_or(64);
|
||||
|
|
|
|||
228
gui/wix/main.wxs
Normal file
228
gui/wix/main.wxs
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
<?xml version='1.0' encoding='windows-1252'?>
|
||||
<!--
|
||||
Copyright (C) 2017 Christopher R. Field.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<!--
|
||||
The "cargo wix" subcommand provides a variety of predefined variables available
|
||||
for customization of this template. The values for each variable are set at
|
||||
installer creation time. The following variables are available:
|
||||
|
||||
TargetTriple = The rustc target triple name.
|
||||
TargetEnv = The rustc target environment. This is typically either
|
||||
"msvc" or "gnu" depending on the toolchain downloaded and
|
||||
installed.
|
||||
TargetVendor = The rustc target vendor. This is typically "pc", but Rust
|
||||
does support other vendors, like "uwp".
|
||||
CargoTargetBinDir = The complete path to the directory containing the
|
||||
binaries (exes) to include. The default would be
|
||||
"target\release\". If an explicit rustc target triple is
|
||||
used, i.e. cross-compiling, then the default path would
|
||||
be "target\<CARGO_TARGET>\<CARGO_PROFILE>",
|
||||
where "<CARGO_TARGET>" is replaced with the "CargoTarget"
|
||||
variable value and "<CARGO_PROFILE>" is replaced with the
|
||||
value from the "CargoProfile" variable. This can also
|
||||
be overridden manually with the "target-bin-dir" flag.
|
||||
CargoTargetDir = The path to the directory for the build artifacts, i.e.
|
||||
"target".
|
||||
CargoProfile = The cargo profile used to build the binaries
|
||||
(usually "debug" or "release").
|
||||
Version = The version for the installer. The default is the
|
||||
"Major.Minor.Fix" semantic versioning number of the Rust
|
||||
package.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Please do not remove these pre-processor If-Else blocks. These are used with
|
||||
the `cargo wix` subcommand to automatically determine the installation
|
||||
destination for 32-bit versus 64-bit installers. Removal of these lines will
|
||||
cause installation errors.
|
||||
-->
|
||||
<?if $(sys.BUILDARCH) = x64 or $(sys.BUILDARCH) = arm64 ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||
<?else ?>
|
||||
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||
<?endif ?>
|
||||
|
||||
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
|
||||
|
||||
<Product
|
||||
Id='*'
|
||||
Name='terrafier-gui'
|
||||
UpgradeCode='A7365100-6923-4DAC-B4A5-2BD817EB802B'
|
||||
Manufacturer='Loki'
|
||||
Language='1033'
|
||||
Codepage='1252'
|
||||
Version='$(var.Version)'>
|
||||
|
||||
<Package Id='*'
|
||||
Keywords='Installer'
|
||||
Description='Native GUI for Terrafier — interactive world painting with GPU rendering'
|
||||
Manufacturer='Loki'
|
||||
InstallerVersion='450'
|
||||
Languages='1033'
|
||||
Compressed='yes'
|
||||
InstallScope='perMachine'
|
||||
SummaryCodepage='1252'
|
||||
/>
|
||||
|
||||
<MajorUpgrade
|
||||
Schedule='afterInstallInitialize'
|
||||
DowngradeErrorMessage='A newer version of [ProductName] is already installed. Setup will now exit.'/>
|
||||
|
||||
<Media Id='1' Cabinet='media1.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1'/>
|
||||
<Property Id='DiskPrompt' Value='terrafier-gui Installation'/>
|
||||
|
||||
<Directory Id='TARGETDIR' Name='SourceDir'>
|
||||
<Directory Id='$(var.PlatformProgramFilesFolder)' Name='PFiles'>
|
||||
<Directory Id='APPLICATIONFOLDER' Name='terrafier-gui'>
|
||||
|
||||
<!--
|
||||
Enabling the license sidecar file in the installer is a four step process:
|
||||
|
||||
1. Uncomment the `Component` tag and its contents.
|
||||
2. Change the value for the `Source` attribute in the `File` tag to a path
|
||||
to the file that should be included as the license sidecar file. The path
|
||||
can, and probably should be, relative to this file.
|
||||
3. Change the value for the `Name` attribute in the `File` tag to the
|
||||
desired name for the file when it is installed alongside the `bin` folder
|
||||
in the installation directory. This can be omitted if the desired name is
|
||||
the same as the file name.
|
||||
4. Uncomment the `ComponentRef` tag with the Id attribute value of "License"
|
||||
further down in this file.
|
||||
-->
|
||||
<!--
|
||||
<Component Id='License' Guid='*'>
|
||||
<File Id='LicenseFile' Name='ChangeMe' DiskId='1' Source='C:\Path\To\File' KeyPath='yes'/>
|
||||
</Component>
|
||||
-->
|
||||
|
||||
<Directory Id='Bin' Name='bin'>
|
||||
<Component Id='Path' Guid='097E8AD4-180B-4B52-8BFE-04AA942D03A6' KeyPath='yes'>
|
||||
<Environment
|
||||
Id='PATH'
|
||||
Name='PATH'
|
||||
Value='[Bin]'
|
||||
Permanent='no'
|
||||
Part='last'
|
||||
Action='set'
|
||||
System='yes'/>
|
||||
</Component>
|
||||
<Component Id='binary0' Guid='*'>
|
||||
<File
|
||||
Id='exe0'
|
||||
Name='terrafier-gui.exe'
|
||||
DiskId='1'
|
||||
Source='$(var.CargoTargetBinDir)\terrafier-gui.exe'
|
||||
KeyPath='yes'/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
<Feature
|
||||
Id='Binaries'
|
||||
Title='Application'
|
||||
Description='Installs all binaries and the license.'
|
||||
Level='1'
|
||||
ConfigurableDirectory='APPLICATIONFOLDER'
|
||||
AllowAdvertise='no'
|
||||
Display='expand'
|
||||
Absent='disallow'>
|
||||
|
||||
<!--
|
||||
Uncomment the following `ComponentRef` tag to add the license
|
||||
sidecar file to the installer.
|
||||
-->
|
||||
<!--<ComponentRef Id='License'/>-->
|
||||
|
||||
<ComponentRef Id='binary0'/>
|
||||
|
||||
<Feature
|
||||
Id='Environment'
|
||||
Title='PATH Environment Variable'
|
||||
Description='Add the install location of the [ProductName] executable to the PATH system environment variable. This allows the [ProductName] executable to be called from any location.'
|
||||
Level='1'
|
||||
Absent='allow'>
|
||||
<ComponentRef Id='Path'/>
|
||||
</Feature>
|
||||
</Feature>
|
||||
|
||||
<SetProperty Id='ARPINSTALLLOCATION' Value='[APPLICATIONFOLDER]' After='CostFinalize'/>
|
||||
|
||||
|
||||
<!--
|
||||
Uncomment the following `Icon` and `Property` tags to change the product icon.
|
||||
|
||||
The product icon is the graphic that appears in the Add/Remove
|
||||
Programs control panel for the application.
|
||||
-->
|
||||
<!--<Icon Id='ProductICO' SourceFile='wix\Product.ico'/>-->
|
||||
<!--<Property Id='ARPPRODUCTICON' Value='ProductICO' />-->
|
||||
|
||||
<Property Id='ARPHELPLINK' Value='https://github.com/loki5512344/Terrafier'/>
|
||||
|
||||
<UI>
|
||||
<UIRef Id='WixUI_FeatureTree'/>
|
||||
|
||||
<!--
|
||||
Enabling the EULA dialog in the installer is a three step process:
|
||||
|
||||
1. Comment out or remove the two `Publish` tags that follow the
|
||||
`WixVariable` tag.
|
||||
2. Uncomment the `<WixVariable Id='WixUILicenseRtf' Value='Path\to\Eula.rft'>` tag further down
|
||||
3. Replace the `Value` attribute of the `WixVariable` tag with
|
||||
the path to a RTF file that will be used as the EULA and
|
||||
displayed in the license agreement dialog.
|
||||
-->
|
||||
<Publish Dialog='WelcomeDlg' Control='Next' Event='NewDialog' Value='CustomizeDlg' Order='99'>1</Publish>
|
||||
<Publish Dialog='CustomizeDlg' Control='Back' Event='NewDialog' Value='WelcomeDlg' Order='99'>1</Publish>
|
||||
|
||||
</UI>
|
||||
|
||||
|
||||
<!--
|
||||
Enabling the EULA dialog in the installer requires uncommenting
|
||||
the following `WixUILicenseRTF` tag and changing the `Value`
|
||||
attribute.
|
||||
-->
|
||||
<!-- <WixVariable Id='WixUILicenseRtf' Value='Relative\Path\to\Eula.rtf'/> -->
|
||||
|
||||
|
||||
<!--
|
||||
Uncomment the next `WixVariable` tag to customize the installer's
|
||||
Graphical User Interface (GUI) and add a custom banner image across
|
||||
the top of each screen. See the WiX Toolset documentation for details
|
||||
about customization.
|
||||
|
||||
The banner BMP dimensions are 493 x 58 pixels.
|
||||
-->
|
||||
<!--<WixVariable Id='WixUIBannerBmp' Value='wix\Banner.bmp'/>-->
|
||||
|
||||
|
||||
<!--
|
||||
Uncomment the next `WixVariable` tag to customize the installer's
|
||||
Graphical User Interface (GUI) and add a custom image to the first
|
||||
dialog, or screen. See the WiX Toolset documentation for details about
|
||||
customization.
|
||||
|
||||
The dialog BMP dimensions are 493 x 312 pixels.
|
||||
-->
|
||||
<!--<WixVariable Id='WixUIDialogBmp' Value='wix\Dialog.bmp'/>-->
|
||||
|
||||
</Product>
|
||||
|
||||
</Wix>
|
||||
Loading…
Add table
Add a link
Reference in a new issue