Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  test.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! A renderer for use in tests, which doesn't actually render a GUI but allows programmatic
//! interaction.
//!
//! The [`ui!`](super::ui) macro supports labeling any element with a string identifier, which can
//! be used to access the element in this UI.
//!
//! The [`Interact`] hook must be created to interact with the test UI, before the UI is run and on
//! the same thread as the UI.
//!
//! See how this UI is used in [`crate::test`].

use super::model::{self, Application, Element};
use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::{
    atomic::{AtomicBool, AtomicU8, Ordering::Relaxed},
    mpsc, Arc, Condvar, Mutex,
};

thread_local! {
    static INTERACT: RefCell<Option<Arc<State>>> = Default::default();
}

/// A test UI which allows access to the UI elements.
#[derive(Default)]
pub struct UI {
    interface: Mutex<Option<UIInterface>>,
}

impl UI {
    pub fn run_loop(&self, app: Application) {
        let (tx, rx) = mpsc::channel();
        let interface = UIInterface { work: tx };

        let elements = id_elements(&app);
        INTERACT.with(cc! { (interface) move |r| {
            if let Some(state) = &*r.borrow() {
                state.set_interface(interface);
            }
        }});
        *self.interface.lock().unwrap() = Some(interface.clone());

        // Close the UI when the root windows are closed.
        // Use a bitfield rather than a count in case the `close` event is fired multiple times.
        assert!(app.windows.len() <= 8);
        let mut windows = 0u8;
        for i in 0..app.windows.len() {
            windows |= 1 << i;
        }
        let windows = Arc::new(AtomicU8::new(windows));
        for (index, window) in app.windows.iter().enumerate() {
            if let Some(c) = &window.element_type.close {
                c.subscribe(cc! { (interface, windows) move |&()| {
                    let old = windows
                        .fetch_update(Relaxed, Relaxed, |x| Some(x & !(1u8 << index)))
                        .unwrap();
                    if old == 1u8 << index {
                        interface.work.send(Command::Finish).unwrap();
                    }
                }});
            } else {
                // No close event, so we must assume a closed state (and assume that _some_ window
                // will have a close event registered so we don't drop the interface now).
                windows
                    .fetch_update(Relaxed, Relaxed, |x| Some(x & !(1u8 << index)))
                    .unwrap();
            }
        }

        while let Ok(f) = rx.recv() {
            match f {
                Command::Invoke(f) => f(),
                Command::Interact(f) => f(&elements),
                Command::Finish => break,
            }
        }

        *self.interface.lock().unwrap() = None;
        INTERACT.with(|r| {
            if let Some(state) = &*r.borrow() {
                state.clear_interface();
            }
        });
    }

    pub fn invoke(&self, f: model::InvokeFn) {
        let guard = self.interface.lock().unwrap();
        if let Some(interface) = &*guard {
            let _ = interface.work.send(Command::Invoke(f));
        }
    }
}

/// Test interaction hook.
#[derive(Clone)]
pub struct Interact {
    state: Arc<State>,
}

impl Interact {
    /// Create an interaction hook for the test UI.
    ///
    /// This should be done before running the UI, and must be done on the same thread that
    /// later runs it.
    pub fn hook() -> Self {
        let v = Interact {
            state: Default::default(),
        };
        {
            let state = v.state.clone();
            INTERACT.with(move |r| *r.borrow_mut() = Some(state));
        }
        v
    }

    /// Wait for the render thread to be ready for interaction.
    pub fn wait_for_ready(&self) {
        let mut guard = self.state.interface.lock().unwrap();
        while guard.is_none() && !self.state.cancel.load(Relaxed) {
            guard = self.state.waiting_for_interface.wait(guard).unwrap();
        }
    }

    /// Cancel an Interact (which causes `wait_for_ready` to always return).
    pub fn cancel(&self) {
        let _guard = self.state.interface.lock().unwrap();
        self.state.cancel.store(true, Relaxed);
        self.state.waiting_for_interface.notify_all();
    }

    /// Run the given function on the element with the given type and identity.
    ///
    /// Panics if either the id is missing or the type is incorrect.
    pub fn element<'a, 'b, T: 'b, F, R>(&self, id: &'a str, f: F) -> R
    where
        &'b T: TryFrom<&'b model::ElementType>,
        F: FnOnce(&model::ElementStyle, &T) -> R + Send + 'a,
        R: Send + 'a,
    {
        self.interact(id, move |element: &IdElement| match element {
            IdElement::Generic(e) => Some(f(&e.style, (&e.element_type).try_into().ok()?)),
            IdElement::Window(_) => None,
        })
        .expect("incorrect element type")
    }

    /// Run the given function on the window with the given identity.
    ///
    /// Panics if the id is missing or the type is incorrect.
    pub fn window<'a, F, R>(&self, id: &'a str, f: F) -> R
    where
        F: FnOnce(&model::ElementStyle, &model::Window) -> R + Send + 'a,
        R: Send + 'a,
    {
        self.interact(id, move |element| match element {
            IdElement::Window(e) => Some(f(&e.style, &e.element_type)),
            IdElement::Generic(_) => None,
        })
        .expect("incorrect element type")
    }

    fn interact<'a, 'b, F, R>(&self, id: &'a str, f: F) -> R
    where
        F: FnOnce(&IdElement<'b>) -> R + Send + 'a,
        R: Send + 'a,
    {
        let (send, recv) = std::sync::mpsc::sync_channel(0);
        {
            let f: Box<dyn FnOnce(&IdElements<'b>) + Send + 'a> = Box::new(move |elements| {
                let _ = send.send(elements.get(id).map(f));
            });

            // # Safety
            // The function is run while `'a` is still valid (we wait here for it to complete).
            let f: Box<dyn FnOnce(&IdElements) + Send + 'static> =
                unsafe { std::mem::transmute(f) };

            let guard = self.state.interface.lock().unwrap();
            let interface = guard.as_ref().expect("renderer is not running");
            let _ = interface.work.send(Command::Interact(f));
        }
        recv.recv().unwrap().expect("failed to get element")
    }
}

#[derive(Clone)]
struct UIInterface {
    work: mpsc::Sender<Command>,
}

enum Command {
    Invoke(Box<dyn FnOnce() + Send + 'static>),
    Interact(Box<dyn FnOnce(&IdElements) + Send + 'static>),
    Finish,
}

enum IdElement<'a> {
    Generic(&'a Element),
    Window(&'a model::TypedElement<model::Window>),
}

type IdElements<'a> = HashMap<String, IdElement<'a>>;

#[derive(Default)]
struct State {
    interface: Mutex<Option<UIInterface>>,
    waiting_for_interface: Condvar,
    cancel: AtomicBool,
}

impl State {
    /// Set the interface for the interaction client to use.
    pub fn set_interface(&self, interface: UIInterface) {
        let mut guard = self.interface.lock().unwrap();
        *guard = Some(interface);
        self.waiting_for_interface.notify_all();
    }

    /// Clear the UI interface.
    pub fn clear_interface(&self) {
        *self.interface.lock().unwrap() = None;
    }
}

fn id_elements<'a>(app: &'a Application) -> IdElements<'a> {
    let mut elements: IdElements<'a> = Default::default();

    let mut windows_to_visit: Vec<_> = app.windows.iter().collect();

    let mut to_visit: Vec<&'a Element> = Vec::new();
    while let Some(window) = windows_to_visit.pop() {
        if let Some(id) = &window.style.id {
            elements.insert(id.to_owned(), IdElement::Window(window));
        }
        windows_to_visit.extend(&window.element_type.children);

        if let Some(content) = &window.element_type.content {
            to_visit.push(content);
        }
    }

    while let Some(el) = to_visit.pop() {
        if let Some(id) = &el.style.id {
            elements.insert(id.to_owned(), IdElement::Generic(el));
        }

        use model::ElementType::*;
        match &el.element_type {
            Button(model::Button {
                content: Some(content),
                ..
            })
            | Scroll(model::Scroll {
                content: Some(content),
            }) => {
                to_visit.push(content);
            }
            VBox(model::VBox { items, .. }) | HBox(model::HBox { items, .. }) => {
                for item in items {
                    to_visit.push(item)
                }
            }
            _ => (),
        }
    }

    elements
}

[ Dauer der Verarbeitung: 0.25 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge