mirror of
https://github.com/SerenityOS/serenity
synced 2024-07-21 10:05:32 +00:00
Presenter: Support multiple frames per slide
This commit is contained in:
parent
7c312980b0
commit
fcda397136
|
@ -11,8 +11,10 @@
|
|||
"slides": [
|
||||
{
|
||||
"title": "Introduction",
|
||||
"frame_count": 2,
|
||||
"objects": [
|
||||
{
|
||||
"frame": 0,
|
||||
"type": "text",
|
||||
|
||||
"text": "Welcome to Presenter!",
|
||||
|
@ -25,6 +27,7 @@
|
|||
"text-alignment": "Center"
|
||||
},
|
||||
{
|
||||
"frame": 1,
|
||||
"type": "text",
|
||||
|
||||
"text": "This program is very cool. It supports:\n - Scaling properly to the window\n - Text\n - Switching between slides\n - that's all for now lol",
|
||||
|
@ -40,8 +43,10 @@
|
|||
},
|
||||
{
|
||||
"title": "The Second Slide",
|
||||
"frame_count": 2,
|
||||
"objects": [
|
||||
{
|
||||
"frame": 0,
|
||||
"type": "text",
|
||||
"text": "CatDog likes this program!",
|
||||
|
||||
|
@ -53,6 +58,7 @@
|
|||
"text-alignment": "Center"
|
||||
},
|
||||
{
|
||||
"frame": 1,
|
||||
"type": "image",
|
||||
|
||||
"rect": [50, 50, 200, 100],
|
||||
|
|
|
@ -41,14 +41,20 @@ StringView Presentation::author() const
|
|||
return "Unknown Author"sv;
|
||||
}
|
||||
|
||||
bool Presentation::has_a_next_frame() const
|
||||
bool Presentation::has_next_frame() const
|
||||
{
|
||||
return m_current_slide < u32(m_slides.size() > 1 ? m_slides.size() - 1 : 0);
|
||||
if (m_slides.is_empty())
|
||||
return false;
|
||||
if (m_current_slide.value() < m_slides.size() - 1)
|
||||
return true;
|
||||
return m_current_frame_in_slide < m_slides[m_current_slide.value()].frame_count() - 1;
|
||||
}
|
||||
|
||||
bool Presentation::has_a_previous_frame() const
|
||||
bool Presentation::has_previous_frame() const
|
||||
{
|
||||
return m_current_slide > 0u;
|
||||
if (m_current_slide > 0u)
|
||||
return true;
|
||||
return m_current_frame_in_slide > 0u;
|
||||
}
|
||||
|
||||
void Presentation::next_frame()
|
||||
|
@ -65,7 +71,7 @@ void Presentation::previous_frame()
|
|||
m_current_frame_in_slide.sub(1);
|
||||
if (m_current_frame_in_slide.has_overflow()) {
|
||||
m_current_slide.saturating_sub(1);
|
||||
m_current_frame_in_slide = m_current_slide == 0u ? 0 : current_slide().frame_count() - 1;
|
||||
m_current_frame_in_slide = current_slide().frame_count() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,13 +114,15 @@ ErrorOr<NonnullOwnPtr<Presentation>> Presentation::load_from_file(StringView fil
|
|||
auto presentation = Presentation::construct(size, metadata);
|
||||
|
||||
auto const& slides = maybe_slides.value();
|
||||
unsigned i = 0;
|
||||
for (auto const& maybe_slide : slides.values()) {
|
||||
if (!maybe_slide.is_object())
|
||||
return Error::from_string_view("Slides must be objects"sv);
|
||||
auto const& slide_object = maybe_slide.as_object();
|
||||
|
||||
auto slide = TRY(Slide::parse_slide(slide_object));
|
||||
auto slide = TRY(Slide::parse_slide(slide_object, i));
|
||||
presentation->append_slide(move(slide));
|
||||
i++;
|
||||
}
|
||||
|
||||
return presentation;
|
||||
|
@ -163,9 +171,8 @@ ErrorOr<DeprecatedString> Presentation::render()
|
|||
for (size_t i = 0; i < m_slides.size(); ++i) {
|
||||
HTMLElement slide_div;
|
||||
slide_div.tag_name = "div"sv;
|
||||
TRY(slide_div.style.try_set("display"sv, "none"sv));
|
||||
TRY(slide_div.attributes.try_set("id"sv, DeprecatedString::formatted("slide{}", i)));
|
||||
TRY(slide_div.attributes.try_set("class"sv, "slide"));
|
||||
TRY(slide_div.attributes.try_set("class"sv, "slide hidden"sv));
|
||||
auto& slide = m_slides[i];
|
||||
TRY(slide_div.children.try_append(TRY(slide.render(*this))));
|
||||
main_element.children.append(move(slide_div));
|
||||
|
@ -181,18 +188,29 @@ ErrorOr<DeprecatedString> Presentation::render()
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style><script>
|
||||
function goto(slideIndex, frameIndex) {
|
||||
// FIXME: Honor the frameIndex.
|
||||
let slide;
|
||||
for (slide of document.getElementsByClassName("slide")) {
|
||||
slide.style.display = "none";
|
||||
for (const slide of document.getElementsByClassName("slide")) {
|
||||
slide.classList.add("hidden");
|
||||
}
|
||||
for (const frame of document.getElementsByClassName("frame")) {
|
||||
frame.classList.add("hidden");
|
||||
}
|
||||
|
||||
const slide = document.getElementById(`slide${slideIndex}`);
|
||||
if (slide) slide.classList.remove("hidden");
|
||||
|
||||
for (let i = 0; i <= frameIndex; i++) {
|
||||
for (const frame of document.getElementsByClassName(`slide${slideIndex}-frame${i}`)) {
|
||||
if (frame) frame.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
if (slide = document.getElementById(`slide${slideIndex}`))
|
||||
slide.style.display = "block";
|
||||
}
|
||||
window.onload = function() { goto(0, 0) }
|
||||
</script><body>
|
||||
</script></head><body>
|
||||
)"sv));
|
||||
TRY(main_element.serialize(builder));
|
||||
TRY(builder.try_append("</body></html>"sv));
|
||||
|
|
|
@ -32,8 +32,8 @@ public:
|
|||
unsigned current_slide_number() const { return m_current_slide.value(); }
|
||||
unsigned current_frame_in_slide_number() const { return m_current_frame_in_slide.value(); }
|
||||
|
||||
bool has_a_next_frame() const;
|
||||
bool has_a_previous_frame() const;
|
||||
bool has_next_frame() const;
|
||||
bool has_previous_frame() const;
|
||||
void next_frame();
|
||||
void previous_frame();
|
||||
void go_to_first_slide();
|
||||
|
|
|
@ -128,8 +128,8 @@ void PresenterWidget::update_web_view()
|
|||
void PresenterWidget::update_slides_actions()
|
||||
{
|
||||
if (m_current_presentation) {
|
||||
m_next_slide_action->set_enabled(m_current_presentation->has_a_next_frame());
|
||||
m_previous_slide_action->set_enabled(m_current_presentation->has_a_previous_frame());
|
||||
m_next_slide_action->set_enabled(m_current_presentation->has_next_frame());
|
||||
m_previous_slide_action->set_enabled(m_current_presentation->has_previous_frame());
|
||||
m_full_screen_action->set_enabled(true);
|
||||
m_present_from_first_slide_action->set_enabled(true);
|
||||
} else {
|
||||
|
|
|
@ -9,17 +9,18 @@
|
|||
#include "Presentation.h"
|
||||
#include <AK/JsonObject.h>
|
||||
|
||||
Slide::Slide(Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title)
|
||||
: m_slide_objects(move(slide_objects))
|
||||
Slide::Slide(unsigned frame_count, Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title)
|
||||
: m_frame_count(move(frame_count))
|
||||
, m_slide_objects(move(slide_objects))
|
||||
, m_title(move(title))
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<Slide> Slide::parse_slide(JsonObject const& slide_json)
|
||||
ErrorOr<Slide> Slide::parse_slide(JsonObject const& slide_json, unsigned slide_index)
|
||||
{
|
||||
// FIXME: Use the text with the "title" role for a title, if there is no title given.
|
||||
auto title = slide_json.get_deprecated_string("title"sv).value_or("Untitled slide");
|
||||
|
||||
auto frame_count = slide_json.get_u32("frame_count"sv).value_or(1);
|
||||
auto maybe_slide_objects = slide_json.get_array("objects"sv);
|
||||
if (!maybe_slide_objects.has_value())
|
||||
return Error::from_string_view("Slide objects must be an array"sv);
|
||||
|
@ -31,11 +32,11 @@ ErrorOr<Slide> Slide::parse_slide(JsonObject const& slide_json)
|
|||
return Error::from_string_view("Slides must be objects"sv);
|
||||
auto const& slide_object_json = maybe_slide_object_json.as_object();
|
||||
|
||||
auto slide_object = TRY(SlideObject::parse_slide_object(slide_object_json));
|
||||
auto slide_object = TRY(SlideObject::parse_slide_object(slide_object_json, slide_index));
|
||||
slide_objects.append(move(slide_object));
|
||||
}
|
||||
|
||||
return Slide { move(slide_objects), title };
|
||||
return Slide { frame_count, move(slide_objects), title };
|
||||
}
|
||||
|
||||
ErrorOr<HTMLElement> Slide::render(Presentation const& presentation) const
|
||||
|
|
|
@ -14,17 +14,17 @@
|
|||
// A single slide of a presentation.
|
||||
class Slide final {
|
||||
public:
|
||||
static ErrorOr<Slide> parse_slide(JsonObject const& slide_json);
|
||||
static ErrorOr<Slide> parse_slide(JsonObject const& slide_json, unsigned slide_index);
|
||||
|
||||
// FIXME: shouldn't be hard-coded to 1.
|
||||
unsigned frame_count() const { return 1; }
|
||||
unsigned frame_count() const { return m_frame_count; }
|
||||
StringView title() const { return m_title; }
|
||||
|
||||
ErrorOr<HTMLElement> render(Presentation const&) const;
|
||||
|
||||
private:
|
||||
Slide(Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title);
|
||||
Slide(unsigned frame_count, Vector<NonnullRefPtr<SlideObject>> slide_objects, DeprecatedString title);
|
||||
|
||||
unsigned m_frame_count;
|
||||
Vector<NonnullRefPtr<SlideObject>> m_slide_objects;
|
||||
DeprecatedString m_title;
|
||||
};
|
||||
|
|
|
@ -17,8 +17,9 @@ static DeprecatedString to_css_length(float design_value, Presentation const& pr
|
|||
return DeprecatedString::formatted("{}vw", length_in_vw);
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject const& slide_object_json)
|
||||
ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject const& slide_object_json, unsigned slide_index)
|
||||
{
|
||||
auto frame = slide_object_json.get_u32("frame"sv).value_or(0);
|
||||
auto maybe_type = slide_object_json.get_deprecated_string("type"sv);
|
||||
if (!maybe_type.has_value())
|
||||
return Error::from_string_view("Slide object must have a type"sv);
|
||||
|
@ -26,9 +27,9 @@ ErrorOr<NonnullRefPtr<SlideObject>> SlideObject::parse_slide_object(JsonObject c
|
|||
auto type = maybe_type.value();
|
||||
RefPtr<SlideObject> object;
|
||||
if (type == "text"sv)
|
||||
object = TRY(try_make_ref_counted<Text>());
|
||||
object = TRY(try_make_ref_counted<Text>(Index { slide_index, frame }));
|
||||
else if (type == "image"sv)
|
||||
object = TRY(try_make_ref_counted<Image>());
|
||||
object = TRY(try_make_ref_counted<Image>(Index { slide_index, frame }));
|
||||
else
|
||||
return Error::from_string_view("Unsupported slide object type"sv);
|
||||
|
||||
|
@ -97,6 +98,7 @@ ErrorOr<HTMLElement> Text::render(Presentation const& presentation) const
|
|||
{
|
||||
HTMLElement div;
|
||||
div.tag_name = "div"sv;
|
||||
TRY(div.attributes.try_set("class"sv, DeprecatedString::formatted("frame slide{}-frame{}", m_slide_index, m_frame_index)));
|
||||
div.style.set("color"sv, m_color.to_deprecated_string());
|
||||
div.style.set("font-family"sv, DeprecatedString::formatted("'{}'", m_font_family));
|
||||
div.style.set("font-size"sv, to_css_length(m_font_size_in_pt * 1.33333333f, presentation));
|
||||
|
@ -125,6 +127,7 @@ ErrorOr<HTMLElement> Image::render(Presentation const& presentation) const
|
|||
|
||||
HTMLElement image_wrapper;
|
||||
image_wrapper.tag_name = "div"sv;
|
||||
TRY(image_wrapper.attributes.try_set("class"sv, DeprecatedString::formatted("frame slide{}-frame{}", m_slide_index, m_frame_index)));
|
||||
image_wrapper.children.append(move(img));
|
||||
image_wrapper.style.set("position"sv, "absolute"sv);
|
||||
image_wrapper.style.set("left"sv, to_css_length(m_rect.left(), presentation));
|
||||
|
|
|
@ -21,19 +21,29 @@ struct HTMLElement {
|
|||
|
||||
ErrorOr<void> serialize(StringBuilder&) const;
|
||||
};
|
||||
struct Index {
|
||||
unsigned slide;
|
||||
unsigned frame;
|
||||
};
|
||||
|
||||
// Anything that can be on a slide.
|
||||
class SlideObject : public RefCounted<SlideObject> {
|
||||
public:
|
||||
virtual ~SlideObject() = default;
|
||||
static ErrorOr<NonnullRefPtr<SlideObject>> parse_slide_object(JsonObject const& slide_object_json);
|
||||
static ErrorOr<NonnullRefPtr<SlideObject>> parse_slide_object(JsonObject const& slide_object_json, unsigned slide_index);
|
||||
virtual ErrorOr<HTMLElement> render(Presentation const&) const = 0;
|
||||
|
||||
protected:
|
||||
SlideObject() = default;
|
||||
SlideObject(Index index)
|
||||
: m_frame_index(index.frame)
|
||||
, m_slide_index(index.slide)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void set_property(StringView name, JsonValue);
|
||||
|
||||
unsigned m_frame_index;
|
||||
unsigned m_slide_index;
|
||||
HashMap<DeprecatedString, JsonValue> m_properties;
|
||||
Gfx::IntRect m_rect;
|
||||
};
|
||||
|
@ -44,7 +54,10 @@ public:
|
|||
virtual ~GraphicsObject() = default;
|
||||
|
||||
protected:
|
||||
GraphicsObject() = default;
|
||||
GraphicsObject(Index index)
|
||||
: SlideObject(index)
|
||||
{
|
||||
}
|
||||
virtual void set_property(StringView name, JsonValue) override;
|
||||
|
||||
// FIXME: Change the default color based on the color scheme
|
||||
|
@ -53,7 +66,10 @@ protected:
|
|||
|
||||
class Text final : public GraphicsObject {
|
||||
public:
|
||||
Text() = default;
|
||||
Text(Index index)
|
||||
: GraphicsObject(index)
|
||||
{
|
||||
}
|
||||
virtual ~Text() = default;
|
||||
|
||||
private:
|
||||
|
@ -69,7 +85,10 @@ private:
|
|||
|
||||
class Image final : public SlideObject {
|
||||
public:
|
||||
Image() = default;
|
||||
Image(Index index)
|
||||
: SlideObject(index)
|
||||
{
|
||||
}
|
||||
virtual ~Image() = default;
|
||||
|
||||
private:
|
||||
|
|
Loading…
Reference in a new issue