fyrox-build-tools 1.0.1

Build tools for Fyrox Engine
Documentation
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use fyrox_core::algebra::{Matrix3, Vector2};
use fyrox_core::Uuid;
use fyrox_resource::untyped::ResourceKind;
use fyrox_ui::button::Button;
use fyrox_ui::image::Image;
use fyrox_ui::scroll_viewer::ScrollViewer;
use fyrox_ui::text::Text;
use fyrox_ui::window::{Window, WindowAlignment};
use fyrox_ui::{
    border::BorderBuilder,
    button::{ButtonBuilder, ButtonMessage},
    core::{parking_lot::Mutex, pool::Handle, SafeLock},
    grid::{Column, GridBuilder, Row},
    image::ImageBuilder,
    message::UiMessage,
    scroll_viewer::{ScrollViewerBuilder, ScrollViewerMessage},
    stack_panel::StackPanelBuilder,
    style::{resource::StyleResourceExt, Style},
    text::{TextBuilder, TextMessage},
    texture::{
        TextureImportOptions, TextureMagnificationFilter, TextureMinificationFilter,
        TextureResource, TextureResourceExtension,
    },
    widget::{WidgetBuilder, WidgetMessage},
    window::{WindowBuilder, WindowMessage, WindowTitle},
    BuildContext, HorizontalAlignment, Orientation, Thickness, UserInterface,
};
use std::{
    io::{BufRead, BufReader, Read},
    process::{ChildStderr, ChildStdout},
    sync::{
        atomic::{AtomicBool, Ordering},
        Arc,
    },
};

pub struct BuildWindow {
    window: Handle<Window>,
    active: Arc<AtomicBool>,
    changed: Arc<AtomicBool>,
    log: Arc<Mutex<String>>,
    log_text: Handle<Text>,
    stop: Handle<Button>,
    scroll_viewer: Handle<ScrollViewer>,
    progress_indicator: Handle<Image>,
    angle: f32,
}

impl Drop for BuildWindow {
    fn drop(&mut self) {
        // Prevent the listen thread from being alive after the build window is destroyed.
        self.active.store(false, Ordering::SeqCst);
    }
}

impl BuildWindow {
    pub fn new(project_name: &str, ctx: &mut BuildContext) -> Self {
        let progress_image = TextureResource::load_from_memory(
            Uuid::new_v4(),
            ResourceKind::Embedded,
            include_bytes!("resources/progress.png"),
            TextureImportOptions::default()
                .with_minification_filter(TextureMinificationFilter::LinearMipMapLinear)
                .with_magnification_filter(TextureMagnificationFilter::Linear)
                .with_lod_bias(-1.0),
        )
        .ok();

        let log_text;
        let stop;
        let scroll_viewer;
        let progress_indicator;
        let window = WindowBuilder::new(WidgetBuilder::new().with_width(420.0).with_height(200.0))
            .can_minimize(false)
            .can_close(false)
            .can_maximize(false)
            .open(false)
            .with_content(
                GridBuilder::new(
                    WidgetBuilder::new()
                        .with_child(
                            GridBuilder::new(
                                WidgetBuilder::new()
                                    .with_child(
                                        TextBuilder::new(
                                            WidgetBuilder::new()
                                                .with_margin(Thickness {
                                                    left: 5.0,
                                                    top: 1.0,
                                                    right: 1.0,
                                                    bottom: 1.0,
                                                })
                                                .on_column(0),
                                        )
                                        .with_text(format!(
                                            "Please wait while {project_name} is building..."
                                        ))
                                        .build(ctx),
                                    )
                                    .with_child({
                                        progress_indicator = ImageBuilder::new(
                                            WidgetBuilder::new()
                                                .with_width(16.0)
                                                .with_height(16.0)
                                                .on_column(1)
                                                .with_margin(Thickness {
                                                    left: 1.0,
                                                    top: 1.0,
                                                    right: 4.0,
                                                    bottom: 1.0,
                                                })
                                                .with_clip_to_bounds(false),
                                        )
                                        .with_opt_texture(progress_image)
                                        .build(ctx);
                                        progress_indicator
                                    }),
                            )
                            .add_row(Row::stretch())
                            .add_column(Column::stretch())
                            .add_column(Column::auto())
                            .build(ctx),
                        )
                        .with_child(
                            BorderBuilder::new(
                                WidgetBuilder::new()
                                    .on_row(1)
                                    .with_margin(Thickness::uniform(2.0))
                                    .with_background(ctx.style.property(Style::BRUSH_DARKEST))
                                    .with_child({
                                        scroll_viewer =
                                            ScrollViewerBuilder::new(WidgetBuilder::new())
                                                .with_content({
                                                    log_text =
                                                        TextBuilder::new(WidgetBuilder::new())
                                                            .build(ctx);
                                                    log_text
                                                })
                                                .build(ctx);
                                        scroll_viewer
                                    }),
                            )
                            .build(ctx),
                        )
                        .with_child(
                            StackPanelBuilder::new(
                                WidgetBuilder::new()
                                    .with_horizontal_alignment(HorizontalAlignment::Right)
                                    .on_row(2)
                                    .with_child({
                                        stop = ButtonBuilder::new(
                                            WidgetBuilder::new()
                                                .with_width(100.0)
                                                .with_margin(Thickness::uniform(4.0)),
                                        )
                                        .with_text("Stop")
                                        .build(ctx);
                                        stop
                                    }),
                            )
                            .with_orientation(Orientation::Horizontal)
                            .build(ctx),
                        ),
                )
                .add_row(Row::auto())
                .add_row(Row::stretch())
                .add_row(Row::strict(28.0))
                .add_column(Column::stretch())
                .build(ctx),
            )
            .with_title(WindowTitle::text(format!("Building {project_name}...")))
            .build(ctx);

        Self {
            window,
            log_text,
            log: Arc::new(Default::default()),
            active: Arc::new(AtomicBool::new(false)),
            changed: Arc::new(AtomicBool::new(false)),
            stop,
            scroll_viewer,
            progress_indicator,
            angle: 0.0,
        }
    }

    pub fn listen(&mut self, (stderr, stdout): (ChildStderr, ChildStdout), ui: &UserInterface) {
        let log = self.log.clone();
        self.active.store(true, Ordering::SeqCst);
        let reader_active = self.active.clone();
        let log_changed = self.changed.clone();

        Self::spawn_pipe_pump(stderr, &reader_active, &log_changed, &log);
        Self::spawn_pipe_pump(stdout, &reader_active, &log_changed, &log);

        ui.send(
            self.window,
            WindowMessage::Open {
                alignment: WindowAlignment::Center,
                modal: true,
                focus_content: true,
            },
        );
    }

    fn spawn_pipe_pump(
        mut pipe: impl Read + Send + 'static,
        reader_active: &Arc<AtomicBool>,
        log_changed: &Arc<AtomicBool>,
        log: &Arc<Mutex<String>>,
    ) {
        let reader_active = reader_active.clone();
        let log_changed = log_changed.clone();
        let log = log.clone();
        std::thread::spawn(move || {
            let pipe: &mut dyn BufRead = &mut BufReader::new(&mut pipe);
            while reader_active.load(Ordering::SeqCst) {
                for line in pipe.lines().take(10).flatten() {
                    let mut log_guard = log.safe_lock();
                    log_guard.push_str(&line);
                    log_guard.push('\n');
                    log_changed.store(true, Ordering::SeqCst);
                }
            }
        });
    }

    pub fn reset(&mut self, ui: &UserInterface) {
        self.active.store(false, Ordering::SeqCst);
        self.changed.store(false, Ordering::SeqCst);
        self.log.safe_lock().clear();
        ui.send(self.log_text, TextMessage::Text(Default::default()));
    }

    pub fn destroy(mut self, ui: &UserInterface) {
        self.reset(ui);
        ui.send(self.window, WindowMessage::Close);
    }

    pub fn update(&mut self, ui: &UserInterface, dt: f32) {
        if self.changed.load(Ordering::SeqCst) {
            ui.send(
                self.log_text,
                TextMessage::Text(self.log.safe_lock().clone()),
            );
            ui.send(self.scroll_viewer, ScrollViewerMessage::ScrollToEnd);

            self.changed.store(false, Ordering::SeqCst);
        }

        self.angle += 10.0 * dt;
        ui.send(
            self.progress_indicator,
            WidgetMessage::RenderTransform(
                Matrix3::new_translation(&Vector2::new(8.0, 8.0))
                    * Matrix3::new_rotation(self.angle)
                    * Matrix3::new_translation(&Vector2::new(-8.0, -8.0)),
            ),
        );
    }

    pub fn handle_ui_message(
        self,
        message: &UiMessage,
        ui: &UserInterface,
        on_stop: impl FnOnce(),
    ) -> Option<Self> {
        if let Some(ButtonMessage::Click) = message.data() {
            if message.destination() == self.stop {
                on_stop();
                self.destroy(ui);
                return None;
            }
        }
        Some(self)
    }
}