How do I keep window's size constant while resizing host window on imgui with Rust? - rust

I want contents' size in a imgui window looks always same, but on resizing OS window, imgui window size and its contents scale too. Actually contents looks 'wrong' when resized, this is scaled down figure of 'hello world' example from imgui-rs repository.
I think I should achieve it by using glViewport, but it looks like I have no access to the function or equivalent things, and ContextWrapper::resize has no effect.
Event::NewEvents(_) => {
let now = Instant::now();
imgui.io_mut().update_delta_time(now - last_frame);
last_frame = now;
}
Event::MainEventsCleared => {
let gl_window = display.gl_window();
platform
.prepare_frame(imgui.io_mut(), gl_window.window())
.expect("Failed to prepare frame");
gl_window.window().request_redraw();
}
Event::RedrawRequested(_) => {
let ui = imgui.new_frame();
let mut run = true;
run_ui(&mut run, ui);
if !run {
*control_flow = ControlFlow::Exit;
}
let gl_window = display.gl_window();
let mut target = display.draw();
target.clear_color_srgb(1.0, 1.0, 1.0, 1.0);
platform.prepare_render(ui, gl_window.window());
let draw_data = imgui.render();
renderer
.render(&mut target, draw_data)
.expect("Rendering failed");
target.finish().expect("Failed to swap buffers");
}
Event::WindowEvent {
event: WindowEvent::Resized(size),
..
} => {
display.gl_window().resize(size);
}
What should I do to keep contents' size constant and to render things correctly?
I'm using imgui with glium and winit.

Related

How do I retrieve gamepad button pressed status from navigator.getGamepads() in Rust wasm-bindgen?

web_sys::Navigator.getGamepads() returns Result<Array, JsValue> whereas I was hoping for an array of websys::Gamepad objects.
How do I parse whether a gamepad button is pressed from the wasm-bindgen::JsValue result? Is there some way to convert it to a web_sys::Gamepad object?
Here's the setup for my attempt:
let window = web_sys::window().expect("global window does not exists");
let navigator = window.navigator();
let gamepads: Result<js_sys::Array, wasm_bindgen::JsValue> = navigator.get_gamepads();
for gp in gamepads.unwrap().iter() {
// ... how to parse Gamepad.buttons from JsValue?
}
Reference: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Navigator.html#method.get_gamepads
Convert JsValue into Gamepad by using wasm-bindgen::JsCast.dyn_into()
https://docs.rs/wasm-bindgen/latest/wasm_bindgen/trait.JsCast.html#method.dyn_into
let window = web_sys::window().expect("global window does not exists");
let navigator = window.navigator();
let gamepads = navigator.get_gamepads().unwrap();
for js in gamepads.iter() {
let gp: Gamepad = js.dyn_into().unwrap(); // <= convert here
let buttons = gp.buttons();
for index in 0..buttons.length() {
let js: JsValue = index.into();
console::log_2(&js, &(buttons.get(index)));
}
}

wgpu WGSL compute shader does not appear to be doing anything

I'm trying to follow along the "hello compute" example from wgpu on Windows 10 (with some minor modifications, mainly gutting the shader so it does basically no actual computing), but when I read the buffer at the end, it's always zeroed out.
This is the shader I'm trying to run, it compiles fine and I think it's correct
[[block]]
struct Numbers
{
data: [[stride(4)]] array<u32>;
};
[[group(0), binding(0)]]
var<storage, read_write> numbers: Numbers;
[[stage(compute), workgroup_size(1)]]
fn main()
{
numbers.data[0] = numbers.data[0] + u32(1);
numbers.data[1] = numbers.data[1] + u32(1);
numbers.data[2] = numbers.data[2] + u32(1);
}
As for the wgpu code, it follows the tutorial quite closely:
I get the instance, device, and queue
let instance = Instance::new(Backends::PRIMARY);
let adapter = block_on(instance
.request_adapter(&RequestAdapterOptions
{
power_preference: PowerPreference::default(),
compatible_surface: None,
}))
.unwrap();
let (device, queue) = block_on(adapter
.request_device(&Default::default(), None))
.unwrap();
Compile the shader and make a pipeline:
let shader = device.create_shader_module(&ShaderModuleDescriptor
{
label: Some("shader"),
source: ShaderSource::Wgsl(shader_src.into()),
});
let pipeline = device.create_compute_pipeline(&ComputePipelineDescriptor
{
label: None,
layout: None,
module: &shader,
entry_point: "main",
});
Make the staging and storage buffer. The dbg!(size) prints 12, which should be correct for a 3-length array for 4-byte u32s.
let buffer = [1u32, 2, 3];
let size = std::mem::size_of_val(&buffer) as u64;
dbg!(size);
let staging_buffer = device.create_buffer(&BufferDescriptor
{
label: None,
size: size,
usage: BufferUsages::MAP_READ | BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let storage_buffer = device.create_buffer_init(&BufferInitDescriptor
{
label: Some("storage buffer"),
contents: cast_slice(&buffer),
usage: BufferUsages::STORAGE
| BufferUsages::COPY_DST
| BufferUsages::COPY_SRC,
});
set up the bind group:
let bg_layout = pipeline.get_bind_group_layout(0);
let bind_group = device.create_bind_group(&BindGroupDescriptor
{
label: None,
layout: &bg_layout,
entries: &[BindGroupEntry
{
binding: 0,
resource: storage_buffer.as_entire_binding(),
}]
});
Get the encoder and create the compute pass. The copy_buffer_to_buffer should copy the storage buffer to the staging buffer so I can read it at the end.
let mut encoder = device.create_command_encoder(&CommandEncoderDescriptor
{
label: None,
});
{
let mut cpass = encoder.begin_compute_pass(&ComputePassDescriptor
{
label: None
});
cpass.set_pipeline(&pipeline);
cpass.set_bind_group(0, &bind_group, &[]);
cpass.dispatch(1, 1, 1);
}
encoder.copy_buffer_to_buffer(
&storage_buffer, 0,
&staging_buffer, 0,
size);
queue.submit(Some(encoder.finish()));
And then submit the compute pass and block for the result:
let buf_slice = staging_buffer.slice(..);
let buf_future = buf_slice.map_async(MapMode::Read);
device.poll(Maintain::Wait);
if let Ok(()) = block_on(buf_future)
{
let data = buf_slice.get_mapped_range();
let result = cast_slice::<u8, u32>(&data).to_vec();
drop(data);
staging_buffer.unmap();
println!("{:?}", result);
}
else
{
println!("error");
}
The error case isn't reached, and the program terminates with no errors, but the result is always printed [0, 0 ,0], when it should be [2, 3, 4].
What am I doing wrong?
The program works fine when I'm running it on my discrete graphics card, but wgpu is bugged on my integrated Intel HD Graphics 630, which is why the program appeared not to work.

How to switch screens in a rust tui app (termion + tui-rs)

I have a tui app where a user is presented with some choices through a list. Once they navigate to the choice they want and hit enter I'd like to take them to the "next" screen.
It's more complicated than just clearning existing text and printing new one because I also need to replace keybindings and basically start a new tui-rs loop. More below.
Code for Screen 1:
pub fn draw_screen() -> Result<(), Box<dyn Error>> {
// Terminal initialization
let stdout = io::stdout().into_raw_mode()?;
let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let events = Events::new();
loop {
terminal.draw(|f| {
// user shown a list they can navigate through using arrow keys
});
match events.next()? {
Event::Input(input) => match input {
Key::Char('q') => {
break;
}
Key::Char('\n') => {
// this is where I need to "send" them to a new screen
}
Key::Down => {
// my_list won't exist on next screen
my_list.items.next();
}
Key::Up => {
my_list.items.previous();
}
_ => {}
},
_ => {}
}
}
Ok(())
}
As can be seen the keybindings at the bottom are specific to this screen. Eg on the next screen there's not going to be a my_list and instead there might be a my_another_list or my_box or nothing at all.
So if all I did was clear the text, I'd still be left inside the same loop with the same keybindings - doesn't work.
What's the right way to initiate a new loop with fresh keybindings?

wbkgd does not set background color

I'm using Rust to write an ncurses app.
I'm trying to set the color of a subwin, however having no success. I'm not even sure the window is created in the first place, or it just doesn't want to set the color.
Here's a minimal example:
use ncurses::*;
fn main() {
setlocale(LcCategory::all, "");
initscr();
keypad(stdscr(), true);
start_color();
init_pair(1, COLOR_RED, COLOR_RED);
loop {
let user_input = get_wch();
match user_input.unwrap() {
WchResult::Char(ch) => {
match ch {
27 => break,
_ => {}
}
},
WchResult::KeyCode(code) => {
match code {
KEY_F5 => {
let ln = subwin(stdscr(), LINES(), 5, 0, 0);
wbkgd(ln, COLOR_PAIR(1));
refresh();
},
_ => {}
}
}
}
}
endwin();
}
As you can see, I initialized a color pair and invoked start_colors().
What could be the issue?
I think the problem might be that your not refreshing the sub-window. Try using wrefresh(ln) instead. Actually use both refresh and refresh(ln).
In this chunk
let ln = subwin(stdscr(), LINES(), 5, 0, 0);
wbkgd(ln, COLOR_PAIR(1));
refresh();
the refresh overwrites the result from the subwin. Also, you would get better results by ORing the COLOR_PAIR with a space (see this).
Addressing the comments:
let user_input = get_wch();
also does a refresh (overwriting the result from the subwin).

Set frame redraw rate in glium?

I am trying to make a game loop using glium in rust. My goal is to get the screen to redraw 60 times per second. With the current event loop code I have, the frame only gets redrawn when the window size changes. I read in the glutin docs, that I need to call request_redraw somewhere, but I'm not sure how/where. This is my code so far:
event_loop.run(move |event, _target, control_flow| match event {
Event::LoopDestroyed => return,
Event::WindowEvent {
window_id: _window_id,
event: winevent,
} => match winevent {
WindowEvent::Resized(physical_size) => display.gl_window().resize(physical_size),
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
_ => {}
},
Event::RedrawRequested(_window_id) => {
let mut target = display.draw();
target.clear_color_srgb(rng.gen(), rng.gen(), rng.gen(), 1.0);
target.finish().unwrap();
}
_ => {}
});
I haven't used glium before (I've been making some graphics applications directly off of Vulkano for a while). However, perusing the API, it seems you can get your Window handle from winit by going through a series of apis. I outlined them in the below code. Something like the below should work for you. The key is getting access to the Window handle from winit. Scrolling through the Window API you should see this: request_redraw. You can then insert game-loop logic around your event handler like this:
use std::time::Instant;
use glium::Display;
use winit::event_loop::{EventLoop, ControlFlow};
use winit::event::{Event, WindowEvent};
use winit::window::Window;
const TARGET_FPS: u64 = 60;
/* ... some function for main loop ... */
let display: Display = ... /* glium Display instance */
event_loop.run(move |event, _target, control_flow| {
let start_time = Instant::now();
match event {
Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
*control_flow = ControlFlow::Exit;
},
...
/*
* Process events here
*/
}
match *control_flow {
ControlFlow::Exit => (),
_ => {
/*
* Grab window handle from the display (untested - based on API)
*/
display.gl_window().window().request_redraw();
/*
* Below logic to attempt hitting TARGET_FPS.
* Basically, sleep for the rest of our milliseconds
*/
let elapsed_time = Instant::now().duration_since(start_time).as_millis() as u64;
let wait_millis = match 1000 / TARGET_FPS >= elapsed_time {
true => 1000 / TARGET_FPS - elapsed_time,
false => 0
};
let new_inst = start_time + std::time::Duration::from_millis(wait_millis);
*control_flow = ControlFlow::WaitUntil(new_inst);
}
}
});

Resources